NC312223 小橙的异或和
题意
解题思路
这题需要在线维护操作一,不妨尝试手动用操作一,操作一段数,会发现除了a[l],其他都至少被除以2了(其实是,除以2, 3, 4, 5),又发现是下取整,也就是说,这个对于每个a[i]只要不一直在a[l]的位置上,在\(log(a[i])\)的次数内,一定会变成0,又因为这是异或和,是否异或0对结果是不会造成影响的。
所以在操作一(0除以任何不为0的数都为0)与操作二(0对异或和不造成影响)都是可以忽略0的。
因为a[i]最大是1e9,也就是只要同一个a[i]没有一直为a[l]的话,每一个a[i]变成0的次数的上限就是\(log(a[i])\),a[i] = 1e9时,这个数大概是30,也就是每一个数字被操作了30以下后(不是一直为a[l]),就变成0了,就可以被跳过了。
那如果操作一区间长度一直是1,怎么办?这样数组一直都不会变,那操作二每次遍历整个数组会不会超时?
结论是,对于操作二,每次操作如果遍历整个数组得到结果的话,必然超时(与下面的做法相比,做了大量重复操作,没有被修改的数字也要重新参与计算)。
为了解决这个情况,不妨维护一个ans,初始化为一开始整个数组的异或和。
在执行操作一的修改每个a[i]的过程中,可以这样:
ans ^= a[i]; // 消除修改前a[i]对ans的影响。
a[i] = a[i] / (i - l + 1); // 修改a[i]。
ans ^= a[i]; // 让新的a[i]对异或和造成影响。
这种情况,让区间少,修改就也少,避免了重复修改带来的时间累赘。
那如何跳过0呢,传统的遍历最多碰到0不执行,但是仍然会遍历到啊?
用并查集。
为什么是并查集呢?因为他把形如000...(一推0)X(末尾)看作了一个整体(X为正整数),而x就是这个并查集的代表元。
如果这个x的下标为i,那么find(i + 1) 就直接找到了,下一段的代表元,从而绕过了0。
如下图这样。

这会有一种边界情况,比如全0了,代表元是谁?
创建虚拟节点n + 1,他的下标超过了所有区间的r,因此到这时,所有操作一会被直接跳过。
代码如下:
#include <bits/stdc++.h>
using namespace std;
#define int long long
constexpr int N = 2e5 + 2;
int a[N], fa[N];
int find(int x) {
if (x == fa[x]) return x;
return fa[x] = find(fa[x]);
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int n, q;
cin >> n >> q;
int ans = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
ans ^= a[i];
fa[i] = i;
}
fa[n + 1] = n + 1;
while (q--) {
int op;
cin >> op;
if (op == 1) {
int l, r;
cin >> l >> r;
for (int i = find(l); i <= r; i = find(i + 1)) {
ans ^= a[i];
a[i] = a[i] / (i - l + 1);
ans ^= a[i];
if (!a[i]) fa[i] = find(i + 1);
}
} else {
cout << ans << '\n';
}
}
}
难点
1.想到用并查集解决跳过元素。
总结
1.如果需要的结果是异或和,遇到修改一个a[i],然后得到修改后的异或和的题目,不用重新遍历求结果,可以利用异或两个一样的数等于没有异或。
2.遇到需要跳过(忽略)元素的题目,可以用并查集。让需要忽略的元素连接上代表元,跳过忽略的也就是查找后面的代表元,也就是跳过无效的不好想,就看看能不能找到下一个有效的(有点类似正难即反,而并查集的运用感觉有一点像分段(不要只专注元素个体,更要关注元素与元素之间的联系,比如他们同处同一个连续段、相邻等关系))。
对以后做题的启发
当一个区间操作会使元素值快速衰减到某个稳定状态(如零)时,可以用并查集维护‘下一个有效位置’,跳过已稳定的元素,从而将暴力遍历优化到近乎线性。
这题有一个不到 $ log $ 的”log“。
浙公网安备 33010602011771号