「解题报告」石子游戏

题目大意

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\) 的和

注意这个定义很关键,一定要仔细体会。

我们可以得出以下式子:

\[f_{i,j} = f(i+2^{j+1},j) + \sum_{i = i + 2^j}^{i + 2^{j+1}-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;
}
posted @ 2023-01-18 21:50  APJifengc  阅读(175)  评论(0编辑  收藏  举报