【题解(异或拆位 trick)】GDKOI2016 魔卡少女 + CF242E XOR on Segment
GDKOI2016 魔卡少女 + CF242E XOR on Segment
是两道题的题解
这两道题都用了异或拆位的套路
GDKOI2016 魔卡少女
题面
给定一个长度为 的序列 , 次操作,操作内容如下
- 给定 ,将 修改为 。
- 给定 ,询问区间 的魔法效果。
其中,魔法效果的定义为: 中所有 连续子序列的异或和 之和。
。
题解
注意到值域很小,,对应的二进制最多有 位(),考虑对所有 个二进制位维护一颗线段树。
对于修改,由于异或每一位的运算独立,所以对于 二进制下的所有位单独修改即可。
对于询问,我们考虑从所有 个二进制位,最终答案是所有二进制位的贡献之和(这是因为位与位之间独立,即不进位)。
对于第 个二进制位,贡献为 ,其中 表示 中所有异或和 的子区间的数量。
- 理解:仅考虑第 位,所有位置上的数 ,所以所有子区间异或和 。联系题目所求,可以知道对于异或和为 的子区间,贡献为 (二进制下第 位实际为 );异或和为 的没有贡献。所以第 位的贡献位 。
考虑怎么在线段树上维护子区间异或和为 的数量,我们可以维护这些值
-
:表示连续子序列从左端点(不包含右端点)开始异或和为 的子序列数量。
-
:表示连续子序列从右端点(不包含左点)开始异或和为 的子序列数量。
-
:区间异或和。
-
:即上文的”子区间异或和为 的数量“。
转移如下
// Node 是线段树上的一个结点对应的结构体 friend Node operator + (Node lhs, Node rhs) { // lhs左区间,rhs右区间 Node res; res.sum = lhs.sum ^ rhs.sum; res.l[0] = lhs.l[0] + rhs.l[lhs.sum]; // "+ rhs.l[lhs.sum]"解释:如果lhs异或和为1,那么rhs也为1,异或起来才是0 res.l[1] = lhs.l[1] + rhs.l[lhs.sum ^ 1]; // 同上,反之亦然 res.r[0] = rhs.r[0] + lhs.r[rhs.sum]; // 同理 res.r[1] = rhs.r[1] + lhs.r[rhs.sum ^ 1]; // 同理 res.ans = lhs.ans + rhs.ans + 1ll * lhs.r[0] * rhs.l[1] + 1ll * lhs.r[1] * rhs.l[0]; // "+ 1ll * lhs.r[0] * rhs.l[1]"解释:lhs右边异或和为0,rhs左边异或和为1,总异或和为1 return res; }
然后就做完了,时间和空间复杂度都是 。
总结:异或拆位套路
- 异或位与位之间独立,所以可以拆开处理。
- 值域不能太大,否则线段树开不下,时间也不太能过。
- 考虑每一位贡献的时候,记得 。
代码
点击查看代码
#include<bits/stdc++.h> using namespace std; using i64 = long long; const int N = 1E5 + 5; const i64 P = 1E8 + 7; int n, m; struct Node { int l[2], r[2]; i64 sum, num; // sum是异或和,num是所有连续子区间异或为1的数量 Node() { l[0] = l[1] = r[0] = r[1] = sum = num = 0; } Node(int x) { l[1] = r[1] = sum = num = x; l[0] = r[0] = x ^ 1; } friend Node operator + (Node lhs, Node rhs) { Node res; res.sum = lhs.sum ^ rhs.sum; res.l[0] = lhs.l[0] + rhs.l[lhs.sum]; res.l[1] = lhs.l[1] + rhs.l[lhs.sum ^ 1]; res.r[0] = rhs.r[0] + lhs.r[rhs.sum]; res.r[1] = rhs.r[1] + lhs.r[rhs.sum ^ 1]; res.num = lhs.num + rhs.num + 1ll * lhs.r[0] * rhs.l[1] + 1ll * lhs.r[1] * rhs.l[0]; return res; } }; struct SegementTree { #define lson (rt << 1) #define rson (rt << 1 | 1) #define mid ((l + r) >> 1) Node t[N * 4]; void update(int rt, int l, int r, int x, int val) { if(l == r) { t[rt] = Node(val); return; } if(x <= mid) { update(lson, l, mid, x, val); } else { update(rson, mid + 1, r, x, val); } t[rt] = t[lson] + t[rson]; } Node query(int rt, int l, int r, int L, int R) { if(L <= l && r <= R) { return t[rt]; } Node res; if(L <= mid) { res = res + query(lson, l, mid, L, R); } if(R > mid) { res = res + query(rson, mid + 1, r, L, R); } return res; } #undef mid } seg[10]; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin >> n; for(int i = 1; i <= n; ++i) { int x; cin >> x; for(int j = 0; j < 10; ++j) { seg[j].update(1, 1, n, i, (x >> j) & 1); } } cin >> m; while(m--) { char opt[10]; cin >> opt; if(opt[0] == 'Q') { int l, r; cin >> l >> r; i64 num = 0; for(int j = 0; j < 10 ; ++j) { Node now = seg[j].query(1, 1, n, l, r); num += now.num << j; } cout << num % P << "\n"; } else { int x, val; cin >> x >> val; for(int j = 0; j < 10; ++j) { seg[j].update(1, 1, n, x, (val >> j) & 1); } } } return 0; }
CF242E XOR on Segment
其实比上一题还简单一些,可以当作练习。
题面
给定长度为 的序列 。有 次操作,操作内容如下:
- 给定 ,求 。
- 给定 ,将 ~ 异或上 。
。
题解
由于修改操作是区间异或,我们联想到了刚才的拆位。,需要拆成 位,感觉可以接受。
对于修改,相当于每一位区间异或 ,考虑用线段树维护区间和。
维护这个比上一题简单多了,统计区间内 与 的数量,根据异或值的不同更新这两个值即可。
询问区间和相信大家都会,此处略去。时间和空间复杂度都是 。
代码
点击查看代码
#include<bits/stdc++.h> using namespace std; using i64 = long long; const int N = 1E5 + 5; const int M = 5E4 + 4; int n, m; struct SegmentTree { #define lson (rt << 1) #define rson (rt << 1 | 1) #define mid ((l + r) / 2) i64 sum[N * 4][2], tag[N * 4]; void pushdown(int rt, int l, int r) { if(tag[rt] != -1) { tag[lson] = tag[lson] == -1 ? tag[rt] : tag[lson] ^ tag[rt]; tag[rson] = tag[rson] == -1 ? tag[rt] : tag[rson] ^ tag[rt]; // [l, mid] 01 更新 int one, zero; one = sum[lson][tag[rt] ^ 1]; zero = sum[lson][tag[rt]]; sum[lson][0] = zero; sum[lson][1] = one; // [mid + 1, r] 01 更新 one = sum[rson][tag[rt] ^ 1]; zero = sum[rson][tag[rt]]; sum[rson][0] = zero; sum[rson][1] = one; // -1 为 tag[rt] 标记空值 tag[rt] = -1; } } void pushup(int rt) { sum[rt][0] = sum[lson][0] + sum[rson][0]; sum[rt][1] = sum[lson][1] + sum[rson][1]; } void build(int rt, int l, int r) { tag[rt] = -1; sum[rt][0] = r - l + 1; // 一开始全部赋值为 0 if(l == r) { return; } build(lson, l, mid); build(rson, mid + 1, r); pushup(rt); } void update(int rt, int l, int r, int L, int R, i64 val) { if(L <= l && r <= R) { tag[rt] = tag[rt] == -1 ? val : tag[rt] ^ val; // [l, r] 01 更新 int one = sum[rt][val ^ 1], zero = sum[rt][val]; sum[rt][0] = zero; sum[rt][1] = one; return; } pushdown(rt, l, r); if(L <= mid) { update(lson, l, mid, L, R, val); } if(R > mid) { update(rson, mid + 1, r, L, R, val); } pushup(rt); } int query(int rt, int l, int r, int L, int R) { if(L <= l && r <= R) { return sum[rt][1]; } pushdown(rt, l, r); int res = 0; if(L <= mid) { res += query(lson, l, mid, L, R); } if(R > mid) { res += query(rson, mid + 1, r, L, R); } return res; } #undef mid } seg[20]; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin >> n; for(int i = 0; i < 20; ++i) { seg[i].build(1, 1, n); // 初始化所有二进制位上的线段树 } for(int i = 1; i <= n; ++i) { int x; cin >> x; for(int j = 0; j < 20; ++j) { seg[j].update(1, 1, n, i, i, (x >> j) & 1); } } cin >> m; for(int i = 1; i <= m; ++i) { int opt, l, r; cin >> opt >> l >> r; if(opt == 1) { i64 ans = 0; for(int j = 0; j < 20; ++j) { ans += 1ll * (1 << j) * seg[j].query(1, 1, n, l, r); } cout << ans << '\n'; } else { i64 x; cin >> x; for(int j = 0; j < 20; ++j) { seg[j].update(1, 1, n, l, r, (x >> j) & 1); } } } return 0; }
本文作者:hzy1
本文链接:https://www.cnblogs.com/hzy1/p/16222523.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步