「解题报告」石子游戏

题目大意

Alice 和 Bob 在 n 堆石子中取石子, 这 n 堆石子的数量分别是 ai, Alice 先取, 每人只能取 1x 个, 假如双方都使用最优策略, 求当 x1n 中的每个数字时谁必胜。
1<N<5×105

样例输入

4
1 2 3 4

样例输出

Bob Alice Bob Alice

题解

本题需要分两部分来解答。

首先,当 x 固定时, 如何求得谁必胜?

我们可以想办法来构造一个 Nim 游戏, 这样就可以知道谁必胜。

Nim 游戏的要求是每人可以取任意数量的石子, 而本题是固定数量, 所以需要进行一定的调整。

我们可以将每一堆分为若干个 x+1 和若干个小于等于 x 的数, 这样小于等于 x 的数都是可以任意取的。 对于每一堆 x+1 个石子, Alice 取任意数量, Bob 都可以将其取完, 且一定会取完。

举个例子, 有两堆 35, x=1。 我们可以分为 2,1,2,2,1 几堆。 Alice 此时必输, 因为 11=0, 当 Alice 选第一堆 1 个, Bob 也可以直接取 1 个将其取完。所以最后一定落到 Alice 先手, Alice 必输。

那么我们就将问题转换为了, 对于 x[1,n], 求每个 i=1naimodx+1

我们可以对每一位进行运算,再统计每一位是 1 的数量, 就能知道异或和是否为 0

cii 出现的次数, y=x+1kny, 对于 m[ky,(k+1)y)mmody=mky。这样的 k 的数量是一个调和级数, 复杂度是 O(nlogn) 的。 一共有 O(logn) 位, 所以复杂度为 O(nlog2n)

现在的问题是如何在 O(1) 的时间内求出 m[ky,(k+1)y)且m-ky第j位为1ci

考虑第 j 位为 1 数字的分布。

000
001
010
011
100
101
110
111

不难发现,对于第 j 位(从 0 开始), 一定有连续的长度为 2j 个数字第 j 位为0, 然后 2j 个数字第 j 位为 1

所以我们可以定义以下数组:

fi,j 表示对于 mimij 位为 1ci 的和

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

我们可以得出以下式子:

fi,j=f(i+2j+1,j)+i=i+2ji+2j+11ci

这个式子什么意思呢?
我们可以画个图:

| 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[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 段的长度为 2j+1 , 所以最后多出的一段长度为 ymod2j+1, 假如它大于 2j, 那么就说明落在 1 上, 加上多出的一段的 ci 和就可以, 使用前缀和计算很容易能 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 @   APJifengc  阅读(209)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示