1 条题解
-
0
自动搬运
来自洛谷,原作者为

Nangu
加 训搬运于
2025-08-24 22:39:51,当前版本为作者最后更新于2025-03-14 19:34:46,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
我们考虑对 进行扫描线。
设当前枚举到右端点 , 表示左端点为 时二元组的权值, 表示数组 的历史和,则对于询问 答案为 。
我们需要做的操作:
- 对于 ,。
- 对于 ,。
- 查询 的区间异或和。
考虑序列分块。我们先预处理出 表示第 块 的值,其中 。
对于第一类操作:
- 散块:暴力修改,暴力重构。
- 整块:我们维护一个 数组,每次修改整块时
tag[o]++,若 ,须重构整块。
对于第二类操作:
- 散块:暴力重构。
- 整块:直接令 ,同时维护一个桶 ,每次将 加入 ,用于维护 。
第三类是平凡的。
现在我们只剩下了一个问题:如何暴力重构整块?
我们需要干两件事:重新计算 的值,以及将懒标记下放更新 的值。
更新 的值相当于对于每一个在桶内的值 ,执行 。容易发现这个过程和计算 的过程非常像,因此我们下文只讨论 的处理方法。
注意到 可以看作 再加上 所产生的每一个进位。因此,我们可以逐位计算 的进位的个数,则原问题就转化为求对于每一个 的正整数幂 以及 ,计算 的个数。
这样直接枚举值域并双指针的做法是 的,考虑进行一点优化。
我们将 分成 和 两类。
对于第一类 ,令 表示 的 在 上的贡献。这个数组的总元素个数是 的。容易线性求出。
对于第二类 ,容易发现,此时 。发现对于任意的 , 对一些 造成贡献的一定是 的一段前缀,且这段前缀里 的值域完全相同,于是直接前缀和统计即可。
如果你看到这里一脸蒙逼,请结合下面的代码一起食用。const int B=511; void rebuild(const int o){ const int l=bl[o], r=br[o]; rep(i, l, r) num[a[i]&B]^=1;//开一个桶 per(k, 8, 0){ int s=1<<k+1, now=0; rep(i, 1, s-1){ now^=num[s-i]; if(now) pre[k][i]=s;//pre[k][i] 表示在 模 2^k 下,i 的贡献 } rep(i, 1, s-1) if(i>>k&1) num[i^1<<k]^=num[i], num[i]=0; } num[0]=0; rep(k, 0, 7){ const int s=1<<k+1; rep(i, 1, s-1){ pre[k+1][i]^=pre[k][i]; pre[k+1][i|s]^=pre[k][i]; //进行前缀和 pre[k][i]=0; } } rep(i, 0, B) f[o][i]=pre[8][i], pre[8][i]=0;//算出第一部分(x<=B)的贡献 rep(i, l, r){ int p=__builtin_ctz((a[i]>>9)^O); if(p) num[(1<<10)-(a[i]&1023)]^=(1<<10+p)-(1<<10); /* 第二部分。 此时 x<=B<=k/2 想要让 x+a_i%k>=k,a_i 第10,11,12一直到log2(k)位必须为1 这就是为什么“贡献一定是一个k的前缀” p即为 a_i 的最长连续1段的长度 */ } int now=0; rep(i, 0, B) now^=num[i], num[i]=0, f[o][i]^=now; int s=0; rep(i, l, r) s^=a[i];//计算不进位的贡献 rep(i, 0, B){ f[o][i]^=s; if(r-l+1&1) f[o][i]^=i; } }
- 1
信息
- ID
- 8035
- 时间
- 7000ms
- 内存
- 512MiB
- 难度
- 7
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者