「解题报告」石子游戏
题目大意
Alice 和 Bob 在 \(n\) 堆石子中取石子, 这 \(n\) 堆石子的数量分别是 \(a_i\), Alice 先取, 每人只能取 \(1\sim x\) 个, 假如双方都使用最优策略, 求当 \(x\) 为 \(1\sim n\) 中的每个数字时谁必胜。
\(1 < N < 5\times10^5\)样例输入
4 1 2 3 4
样例输出
Bob Alice Bob Alice
题解
本题需要分两部分来解答。
首先,当 \(x\) 固定时, 如何求得谁必胜?
我们可以想办法来构造一个 Nim 游戏, 这样就可以知道谁必胜。
Nim 游戏的要求是每人可以取任意数量的石子, 而本题是固定数量, 所以需要进行一定的调整。
我们可以将每一堆分为若干个 \(x+1\) 和若干个小于等于 \(x\) 的数, 这样小于等于 \(x\) 的数都是可以任意取的。 对于每一堆 \(x+1\) 个石子, Alice 取任意数量, Bob 都可以将其取完, 且一定会取完。
举个例子, 有两堆 \(3\) 和 \(5\), \(x=1\)。 我们可以分为 \(2,1,2,2,1\) 几堆。 Alice 此时必输, 因为 \(1\oplus 1 = 0\), 当 Alice 选第一堆 \(1\) 个, Bob 也可以直接取 \(1\) 个将其取完。所以最后一定落到 Alice 先手, Alice 必输。
那么我们就将问题转换为了, 对于 \(x \in [1,n]\), 求每个 \(\bigoplus_{i=1}^n a_i \bmod x+1\)。
我们可以对每一位进行运算,再统计每一位是 \(1\) 的数量, 就能知道异或和是否为 \(0\)。
设 \(c_i\) 为 \(i\) 出现的次数, \(y = x + 1\), \(k \le \lceil\frac{n}{y}\rceil\), 对于 \(m\in[ky,(k+1)y)\), \(m \bmod y = m - ky\)。这样的 \(k\) 的数量是一个调和级数, 复杂度是 \(\mathrm{O}(n\log n)\) 的。 一共有 \(\mathrm{O}(\log n)\) 位, 所以复杂度为 \(\mathrm{O}(n \log^2 n)\)。
现在的问题是如何在 \(\mathrm{O}(1)\) 的时间内求出 \(\sum_{m \in [ky, (k+1)y) \text{且m-ky第j位为1}}c_i\)。
考虑第 \(j\) 位为 \(1\) 数字的分布。
000
001
010
011
100
101
110
111
不难发现,对于第 \(j\) 位(从 \(0\) 开始), 一定有连续的长度为 \(2^j\) 个数字第 \(j\) 位为0, 然后 \(2^j\) 个数字第 \(j\) 位为 \(1\)。
所以我们可以定义以下数组:
\(f_{i,j}\) 表示对于 \(m\ge i\), \(m - i\) 第 \(j\) 位为 \(1\) 的 \(c_i\) 的和。
注意这个定义很关键,一定要仔细体会。
我们可以得出以下式子:
这个式子什么意思呢?
我们可以画个图:
| 0 | 1 | 0 | 1 | 0 | 1 |
|2^(j+1)| | | | | ···
|2^j|2^j| | | | |
↑ ↑
f(i, j) f(i + 2^(j+1), j)
也就是将当前 \(01\) 段所有 \(1\) 的部分加起来, 再加上下一个 \(01\) 段的 \(1\) 的和。
那么我们如何计算 \(m \in [ky, (k+1)y)\) 的所有和呢?
我们接着画个图:
ky (k+1)y
↓ ↓
| 0 | 1 | 0 | 1 | 0 | 1 | | 0 |1|1| 0 |
|2^(j+1)| | | | | ··· | | | | |
|2^j|2^j| | | | | | | | | |
↑ ↑ ↑ ↑
f(ky, j) f(ky + 2^(j+1), j) ① ②
先看这种情况:我们只需要将 \(f(ky, j)\) 减去 ①往右 的部分, 再加上多减去的 ② 的部分就好了。
每一段 \(01\) 段的长度为 \(2^{j+1}\) , 所以最后多出的一段长度为 \(y \bmod 2^{j+1}\), 假如它大于 \(2^j\), 那么就说明落在 \(1\) 上, 加上多出的一段的 \(c_i\) 和就可以, 使用前缀和计算很容易能 \(\mathrm{O}(1)\) 得出。 如果没有在 \(0\) 上就不用再加东西了。
注意边界可能会有一些加 1 减 1, 自己推一下就可以了。
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2000005;
int n, a, c[MAXN], w[MAXN];
int f[MAXN][20];
int main() {
#ifdef DEBUG
freopen("T1.in", "r", stdin);
freopen("T1.out", "w", stdout);
#else
freopen("stone.in", "r", stdin);
freopen("stone.out", "w", stdout);
#endif
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &c[i]), w[c[i]]++;
for (int i = 1; i <= n; i++) c[i] = c[i - 1] + w[i];
for (int i = n + 1; i <= 3 * n; i++) c[i] = c[n];
for (int i = n; i >= 0; i--) {
for (int j = 0; (1 << j) <= n; j++) {
f[i][j] = f[i + (1 << (j + 1))][j] + c[i + (1 << (j + 1)) - 1] - c[i + (1 << j) - 1];
}
}
for (int x = 1; x <= n; x++) {
int y = x + 1;
for (int j = 0; (1 << j) <= n; j++) {
int cnt = 0;
for (int k = 0; k <= ceil(n / (double) y); k++) {
int q = f[k*y][j] - f[(k+1)*y-(y%(1<<(j+1)))][j] + ((y%(1<<(j+1)) >(1<<j)) ? (c[(k+1)*y - 1] - c[(k+1)*y-y%(1<<(j+1))+(1<<j)-1]) : 0);
cnt += q;
//printf("[%d,%d):%d\n", k*y, (k+1)*y, q);
}
//printf("digit %d: %d\n", j, cnt);
if (cnt % 2 != 0) {
printf("Alice ");
goto next;
}
}
printf("Bob ");
next:
continue;
}
return 0;
}