【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;
}
posted @ 2023-02-22 15:56  あおいSakura  阅读(23)  评论(0编辑  收藏  举报