HDU7149. Alice and Bob (2022杭电多校第1场L题)

HDU7149. Alice and Bob (2022杭电多校第1场L题)

题意

黑板上有若干个非负整数,其中非负整数 \(i\) 的数量为 \(a_i\)

如果黑板上出现 \(0\) 这个数,Alice获胜,若没有进行下面的操作。

Alice首先将这若干个非负整数划分成两个部分(可以有一部分为空,但Alice这样一定会输)。

Bob根据Alice划的两部分,选择其中的一部分擦掉,将另一部分的所有整数减 \(1\)

操作完毕后,如果黑板上出现 \(0\) 这个数,Alice获胜,否则继续上述操作。

直到所有数都擦完,黑板上都没有出现过 \(0\) 这个数,Bob获胜。

假定Alice和Bob都以最优策略进行游戏,问谁会最终赢得游戏。

分析

对于Alice来说,0是唯一的不需要继续操作的必胜态的必要子集,我们可以尝试从0推出所有的必胜态的所有必要子集。

利用一个很显然的性质,如果Alice能做到划分出的两部分数都减掉1,这两部分数对于Alice来讲 都是 必胜态,则划分前的状态也是必胜态。

设一个集合 \(S\),一开始 $ S = {(0)} $。利用上面的性质进行逆推,两个 \((0)\) 可以推出 \((1,1)\),将这个元素加入到集合 \(S\) 中。

变成 \(S = \{(0),(1,1)\}\)
\((0)\)\((1,1)\) 可以推出 \((2,2,1)\)
两个 \((1,1)\) 可以推出 \((2,2,2,2)\)
将这两个元素加入到集合 \(S\) 中。

变成 \(S = \{(0),(1,1),(2,2,1),(2,2,2,2)\}\)
\((0)\)\((2,2,1)\) 可以推出 \((3,3,2,1)\)
\((1,1)\)\((2,2,1)\) 可以推出 \((3,3,2,2,2)\)
两个 \((2,2,1)\) 可以推出 \((3,3,3,3,2,2)\)
\((2,2,2,2)\)\((0)\) 可以推出 \((3,3,3,3,1)\)
\((2,2,2,2)\)\((1,1)\) 可以推出 \((3,3,3,3,2,2)\)
\((2,2,2,2)\)\((2,2,1)\) 可以推出 \((3,3,3,3,3,3,2)\)
\((2,2,2,2)\)\((2,2,2,2)\) 可以推出 \((3,3,3,3,3,3,3,3)\)
将上面推出的 \(7\) 个元素加入到 \(S\) 中。

以此类推……。

推了这么多,很容易发现这个集合里元素的性质。概括且抽象地说,假如我们认为每两个 \(n\),能合并成一个 \(n-1\),那么集合 \(S\) 中的元素一定可以最终合并成元素 \((0)\)。反之,如果某个状态能够合并成状态 \((0)\),那么这个状态一定在这个集合里面(这个“反之”可以想想为什么)。那么这个性质就可以完整描述这个集合了。并且,只要Alice遇到的状态中的某个子集在集合 \(S\) 当中,Alice就赢了。

于是,判断这道题谁赢的方法也很显然了,可以从大到小进行贪心,如果数 \(i\)\(a_i\) 个,那么数 \(i-1\) 的个数(即 \(a_{i-1}\))会增加 \(\lfloor\frac{a_i}{2}\rfloor\) 个。以此类推,最后判断 \(a_0\) 是否大于 \(0\),如果大于 \(0\),Alice赢,否则Bob赢。

代码

#include <iostream>
using namespace std;
const int maxn = 1e6 + 10;
int n;
int num[maxn];
void solve() {
    cin >> n;
    for (int i = 0; i <= n; i++) {
        cin >> num[i];
    }
    for (int i = n; i >= 1; i--) {
        num[i - 1] += (num[i] >> 1);
    }
    if (num[0] > 0) {
        cout << "Alice\n";
    } else {
        cout << "Bob\n";
    }
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    int T;
    cin >> T;
    while (T--) {
        solve();
    }
    return 0;
}
posted @ 2022-07-20 15:11  聆竹听风  阅读(309)  评论(0)    收藏  举报