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;
}