【题解(异或拆位 trick)】GDKOI2016 魔卡少女 + CF242E XOR on Segment

GDKOI2016 魔卡少女 + CF242E XOR on Segment

是两道题的题解

这两道题都用了异或拆位的套路

GDKOI2016 魔卡少女

题面

给定一个长度为 n 的序列 a1,a2,,anm 次操作,操作内容如下

  1. 给定 p,x,将 ap 修改为 x
  2. 给定 l,r,询问区间 [l,r]魔法效果

其中,魔法效果的定义为:[l,r]所有 连续子序列的异或和 之和。

n,m105 , 0ai,x103

没读懂题?查看原题面

题解

注意到值域很小,0ai1000,对应的二进制最多有 10 位(210=1024>1000),考虑对所有 10 个二进制位维护一颗线段树。

对于修改,由于异或每一位的运算独立,所以对于 x 二进制下的所有位单独修改即可。

对于询问,我们考虑从所有 10 个二进制位,最终答案是所有二进制位的贡献之和(这是因为位与位之间独立,即不进位)。

对于第 i 个二进制位,贡献为 2i×num,其中 num 表示 [l,r] 中所有异或和 =1 的子区间的数量。

  • 理解:仅考虑第 i 位,所有位置上的数 {0,1},所以所有子区间异或和 {0,1}。联系题目所求,可以知道对于异或和为 1 的子区间,贡献为 1×2i(二进制下第 i 位实际为 2i);异或和为 0 的没有贡献。所以第 i 位的贡献位 2i×num

考虑怎么在线段树上维护子区间异或和为 1 的数量,我们可以维护这些值

  1. l[0/1]:表示连续子序列从左端点(不包含右端点)开始异或和为 0/1 的子序列数量。

  2. r[0/1]:表示连续子序列从右端点(不包含左点)开始异或和为 0/1 的子序列数量。

  3. sum:区间异或和。

  4. num:即上文的”子区间异或和为 1 的数量“。

转移如下

// 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;
}

然后就做完了,时间和空间复杂度都是 O(nlognlogai)

总结:异或拆位套路

  • 异或位与位之间独立,所以可以拆开处理。
  • 值域不能太大,否则线段树开不下,时间也不太能过。
  • 考虑每一位贡献的时候,记得 ×2i

代码

点击查看代码
#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

其实比上一题还简单一些,可以当作练习。

题面

给定长度为 n 的序列 a1,a2,,an。有 m 次操作,操作内容如下:

  1. 给定 l,r,求 i=lrai
  2. 给定 l,r,x,将 al ~ ar 异或上 x

n105 , m5×104 , 0ai,x106

题解

由于修改操作是区间异或,我们联想到了刚才的拆位。log2ai20,需要拆成 20 位,感觉可以接受。

对于修改,相当于每一位区间异或 0/1,考虑用线段树维护区间和。

维护这个比上一题简单多了,统计区间内 01 的数量,根据异或值的不同更新这两个值即可。

询问区间和相信大家都会,此处略去。时间和空间复杂度都是 O(nlognlogai)

代码

点击查看代码
#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 中国大陆许可协议进行许可。

posted @   hzy1  阅读(73)  评论(2编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起