【YBT2023寒假Day13 B】马可波罗(博弈论)(数学)
马可波罗
题目链接:YBT2023寒假Day13 B
题目大意
有 n 堆石子,每对有 ai 个,两个人轮流取,每次可以选一堆石子,取至少 1 个至多 x 个石子。
无法操作着输。
然后问你当 x 分别是 1~n 时,是先手必胜还是后手必胜。
思路
首先博弈论经典结论是一对石子的 SG 值是 \(a_i\bmod (x+1)\),若干堆石子是每一堆的异或和。
那问题就是对于每个 \(2\leqslant x\leqslant n+1\) 求 \(a_i\bmod x\) 的异或和是否为 \(0\)。
那首先取模不太好处理,把它转一下:
\(a_i\bmod x=a_i-x\left\lfloor\frac{a_i}{x}\right\rfloor\)
于是考虑我们枚举 \(x\) 和 \(0\leqslant k\leqslant \left\lfloor\frac{n}{x}\right\rfloor\)(那这里是 \(n\log n\) 的),考虑算 \([kx,(k+1)x-1]\) 部分的贡献,也就是这里面的数减去 \(kx\) 之后的异或和。
那我们考虑一位一位算贡献,设 \(f_{i,j}\) 为 \(a\) 里面所有 \(\geqslant i\) 的数减去 \(i\) 之后第 \(j\) 位的异或和。
那转移我们就每 \(2^{j+1}\) 的长度考虑一次(因为这样减去 \(i\) 和减去 \(i+x2^{j+1}\) 对于第 \(j\) 位的 \(0/1\) 是没有改变的),设 \(o(l,r)\) 为 \(a\) 数组中值域在 \(l\sim r\) 之间的个数。
那就是:\(f_{i,j}=o(i+2^j,i+2^{j+1}-1)\oplus f_{i+2^{j+1},j}\)
然后看如何统计答案,我们设 \(l=kx,r=(k+1)x-1\),我们就就找到一个最大的 \(z\) 使得 \(l+2^{j+1}z\leqslant r\),那后缀和一下就是 \([l,l+2^{j+1}(z+1)-1]\) 的贡献除去 \([r+1,l+2^{j+1}(z+1)-1]\) 的贡献。
那 \([l,l+2^{j+1}(z+1)-1]\) 这个我们可以直接是 \(f_{l,j}\oplus f_{l+2^{j+1}(z+1),j}\),至于后面那个,因为这里面的长度不超过 \(2^{j+1}\),所以我们可以类似于一个暴力统计,直接看 \(o(\max(r,l+2^{j+1}z+2^j-1)+1,l+2^{j+1}(z+1)-1)\) 就可以了。
(答案就是把这三个异或起来,因为减去在异或中就是异或)
代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 2e6 + 100;
int n, a[N], b[N], m, g[N], f[N][22];
int o(int l, int r) {
if (r < 1) return 0;
if (l > n) return 0;
if (r > n) r = n;
if (l < 1) l = 1;
return g[r] ^ g[l - 1];
}
int re; char c;
int read() {
re = 0; c = getchar();
while (c < '0' || c > '9') c = getchar();
while (c >= '0' && c <= '9') {
re = (re << 3) + (re << 1) + c - '0';
c = getchar();
}
return re;
}
int main() {
freopen("stone.in", "r", stdin);
freopen("stone.out", "w", stdout);
n = read();
for (int i = 1; i <= n; i++) a[i] = read(), g[a[i]] ^= 1;
for (int i = 1; i <= n; i++) g[i] ^= g[i - 1];
for (int i = n; i >= 0; i--) {
for (int j = 0, j2 = 1, j12 = 2; j2 <= n + 1; j++, j2 <<= 1, j12 <<= 1) {
f[i][j] = o(i + j2, i + j12 - 1) ^ f[i + j12][j];
}
}
for (int i = 2; i <= n + 1; i++) {
int ans;
for (int j = 0, j2 = 1, j12 = 2; j2 <= i; j++, j2 <<= 1, j12 <<= 1) {
ans = 0;
for (int k = 0; k * i <= n; k++) {
int l = i * k, r = i * (k + 1) - 1;
int z = (r - l) / j12;
ans ^= f[l][j] ^ f[l + j12 * (z + 1)][j] ^ (o(max(r, l + j12 * z + j2 - 1) + 1, l + j12 * (z + 1) - 1));
}
if (ans) break;
}
if (ans) printf("Alice ");
else printf("Bob ");
}
return 0;
}