【2020ICPC南京J】Just Another Game of Stones(Nim博弈+吉老师线段树)
题目链接:https://ac.nowcoder.com/acm/contest/10272/J
出题人题解链接:https://zhuanlan.zhihu.com/p/338249705
题目大意
给定一串石子堆\(b_{i}\),有如下两种操作:
-
将区间 \([l,r]\) 中的石子堆中的石子数量变为 \(max(b_{i}, x)\)。
-
取出区间 \([l,r]\) 中的石子堆,新增第 \(r-l+2\) 个石子堆,其中石子数量为 \(x\)。询问有多少种方法拿石子使得转变为必胜态。
思路
看到区间变化为 \(max(b_{i}, x)\) -> 吉老师线段树!
Nim博弈中,必胜态为异或和为0,将题意转成有多少种方法拿石子使得最后的异或和为0。
-
若当前 \(r-l+1\) 个石子堆的异或和为 \(0\),则不能够移动石子,输出 \(0\)。
-
若当前 \(r-l+1\) 个石子堆的异或和不为 \(0\)。设当前异或和为 \(sum\),需要操作的石子堆个数为 \(y\),则需要拿走 \(y-sum \bigoplus y\) 个才能成立。然后开始参考出题人题解。先求前 \(r-l+1\) 个数字的 \(sum\) 的最高为 \(1\)的位为 \(1\),再求 \(x\) 能否满足要求。
吉老师线段树部分
本部分我参考了HDU的代码。
在 push_up
操作时,维护一个最小值次小值、二进制当前区间在这个位是1的个数之和。
在 push_tag
操作时,对于异或和,根据最小值的数量的奇偶性来判断是否变化。如果是最小值的数量是奇数,则需要异或上 \(tg \bigoplus fi\_min\) 才行。(\(fi\min\) 是区间最小值),转化过程为:设要异或上的值为 \(z\),则得到 \(fi\_min \bigoplus z = tg\),通过移项可得 \(z = tg \bigoplus fi\_min\);如果最小值的数量为偶数,则什么都不做。然后修改二进制当前区间在这个位是1的个数之和的值。再是吉老师线段树 push_tag
的常规操作。
注意:因为一开始使用的 \(inf\) 设定为 \(0x3f3f3f3f\),一直在RE,事实上这个数字是比 \(2^{30}-1\) 要小的,导致在修改时会一直往下走。
AC代码
#include <bits/stdc++.h>
#define inf (2e9)
using namespace std;
const int MAXN = 4e5 + 10;
int a[MAXN];
class JLS {
public:
struct node {
int l, r;
int fi_min, se_min;
int cnt_min;
int sum, bit[31];
} T[MAXN << 2];
int lazy[MAXN << 2];
#define lson rt<<1
#define rson rt<<1|1
inline void push_up(int rt) {
T[rt].sum = T[lson].sum ^ T[rson].sum;
for (int i = 0; i < 31; i++) T[rt].bit[i] = T[lson].bit[i] + T[rson].bit[i];
if (T[lson].fi_min == T[rson].fi_min) {
T[rt].fi_min = T[lson].fi_min;
T[rt].se_min = min(T[lson].se_min, T[rson].se_min);
T[rt].cnt_min = T[lson].cnt_min + T[rson].cnt_min;
} else if (T[lson].fi_min < T[rson].fi_min) {
T[rt].fi_min = T[lson].fi_min;
T[rt].se_min = min(T[lson].se_min, T[rson].fi_min);
T[rt].cnt_min = T[lson].cnt_min;
} else {
T[rt].fi_min = T[rson].fi_min;
T[rt].se_min = min(T[lson].fi_min, T[rson].se_min);
T[rt].cnt_min = T[rson].cnt_min;
}
}
inline void push_tag(int rt, int tg) {
if (T[rt].fi_min >= tg) return;
T[rt].sum ^= (T[rt].cnt_min & 1) * (T[rt].fi_min ^ tg);
for (int i = 0; i < 31; i++) {
if (T[rt].fi_min >> i & 1)T[rt].bit[i] -= T[rt].cnt_min;
if (tg >> i & 1) T[rt].bit[i] += T[rt].cnt_min;
}
T[rt].fi_min = lazy[rt] = tg;
}
inline void push_down(int rt) {
if (lazy[rt] != -1) {
push_tag(lson, lazy[rt]), push_tag(rson, lazy[rt]);
lazy[rt] = -1;
}
}
void build(int rt, int l, int r) {
T[rt].l = l, T[rt].r = r;
lazy[rt] = -1;
if (l == r) {
T[rt].sum = T[rt].fi_min = a[l];
T[rt].se_min = inf;
T[rt].cnt_min = 1;
for (int i = 0; i < 31; i++) {
T[rt].bit[i] = (a[l] >> i) & 1;
}
return;
}
int mid = (l + r) >> 1;
build(lson, l, mid), build(rson, mid + 1, r);
push_up(rt);
}
void update(int rt, int L, int R, int v) {
// assert(rt < (MAXN<<2));
if (v <= T[rt].fi_min) return;
if (L <= T[rt].l && T[rt].r <= R && T[rt].se_min > v) {
push_tag(rt, v);
return;
}
push_down(rt);
int mid = (T[rt].l + T[rt].r) >> 1;
if (L <= mid) update(lson, L, R, v);
if (R > mid) update(rson, L, R, v);
push_up(rt);
}
int query_sum(int rt, int L, int R) {
if (L <= T[rt].l && T[rt].r <= R) return T[rt].sum;
push_down(rt);
int mid = (T[rt].l + T[rt].r) >> 1;
int ans = 0;
if (L <= mid) ans ^= query_sum(lson, L, R);
if (R > mid) ans ^= query_sum(rson, L, R);
return ans;
}
int query_bit(int rt, int L, int R, int pos) {
if (L <= T[rt].l && T[rt].r <= R) return T[rt].bit[pos];
push_down(rt);
int mid = (T[rt].l + T[rt].r) >> 1;
int ans = 0;
if (L <= mid) ans += query_bit(lson, L, R, pos);
if (R > mid) ans += query_bit(rson, L, R, pos);
return ans;
}
} tree;
int main() {
int n, q;
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
tree.build(1, 1, n);
while (q--) {
int opt, l, r, x;
scanf("%d%d%d%d", &opt, &l, &r, &x);
if (opt == 1) {
tree.update(1, l, r, x);
} else {
int res = tree.query_sum(1, l, r);
res ^= x;
if (res == 0) {
printf("0\n");
} else {
int ans = 0, k = 0;
for (int i = 30; i >= 0; i--) {
if ((res >> i) & 1) {
k = i;
break;
}
}
ans = tree.query_bit(1, l, r, k) + ((x >> k) & 1);
printf("%d\n", ans);
}
}
}
}