题目链接: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 igoplus y) 个才能成立。然后开始参考出题人题解。先求前 (r-l+1) 个数字的 (sum) 的最高为 (1)的位为 (1),再求 (x) 能否满足要求。
吉老师线段树部分
本部分我参考了HDU的代码。
在 push_up
操作时,维护一个最小值次小值、二进制当前区间在这个位是1的个数之和。
在 push_tag
操作时,对于异或和,根据最小值的数量的奇偶性来判断是否变化。如果是最小值的数量是奇数,则需要异或上 (tg igoplus fi\_min) 才行。((fimin) 是区间最小值),转化过程为:设要异或上的值为 (z),则得到 (fi\_min igoplus z = tg),通过移项可得 (z = tg igoplus 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
");
} 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
", ans);
}
}
}
}