石子游戏 IX
难度:简单
每天的痛苦从博弈题开始/(ㄒoㄒ)/~~
为了方便,我们用 A 来代指 Alice,用 B 带代指 Bob。
A 只有一种获胜方式,是使得 B 在选石子时凑成 3 的倍数;而 B 除了能够通过让 A 凑成 3 的倍数以外,还能通过让游戏常规结束来获胜。
因此整个游戏过程,我们只需要关心「已被移除的石子总和」和「剩余石子个数/价值情况」即可。
更进一步的,我们只需关心已被移除的石子总和是否为 3 的倍数,以及剩余石子的价值与已移除石子总和相加是否凑成 3 的倍数即可。
所以我们可以按照石子价值除以 3 的余数分成三类,并统计相应数量。
不失一般性考虑,某个回合开始前,已移除的石子总和状态为 x(共三种,分别为除以 3 余数为 0、1 和 2,其中当状态为 0,且非首个回合时,说明凑成 3 的倍数,游戏结束),剩余石子价值除以 3 的余数 s 分别为 0、1 和 2。
首先如果当前 x = 1 时,不能选择 s = 2 的石子,否则会导致凑成总和为 3 的倍数而失败;同理 x = 2 时,不能选择 s = 1 的石子;而选择 s = 0 的数字,不会改变 x 的状态,可看做换手操作。
同时成对的 s = 0 的等价于没有 s = 0 的石子(双方只需要轮流选完这些 s = 0 的石子,最终会回到先手最开始的局面);而选择与 x 相同的 s 会导致 x 改变(即 x = 1 时,选择 s=1 的石子,会导致 x = 2;而 x=2 时,选 s = 2 的石子,会导致 x = 1)。
明确规则后,是分情况讨论的过程:
- s = 0 的石子数量为偶数:此时等价于没有 s = 0 的石子,我们只需要关心 s = 1 和 s = 2 即可:
- s = 1 的石子数量为 0: 这意味着 A 开始选择的只能是 s = 2,此时交给 B 的局面为「x = 2、剩余石子只有 s = 2」,此时 B 只能选 s = 2 的石子,由于 x = 2 且选择的石子 s = 2,因此交由回 A 的局面为「x = 1,剩余是在只有 s = 2」,因此游戏继续的话 A 必败,同时如果在该过程的任何时刻石子被取完,也是 B 直接获胜,即 A 仍为必败;
- s = 2 的石子数量为 0:分析同理,A 只能选 s = 1,此时交给 B 的局面为「x = 1、剩余石子只有 s = 1」,此时 B只能选 s = 1 的石子,由于 x = 1 且选择的石子 s = 1,因此交由回 A 的局面为「x = 2,剩余是在只有 s = 1」,因此游戏继续的话 A 必败,同时如果在该过程的任何时刻石子被取完,也是 B 直接获胜,即 A 仍为必败;
- s = 1 和 s = 2 的石子数量均不为 0:A 选数量不是最多的一类石子,B 下一回合只能选择相同类型的石子(或是无从选择导致失败),然后游戏继续,最终 B 会先进入「只能凑成 3 的倍数」的局面导致失败,即 A 必胜。
s = 0 的石子数量为奇数:此时等价于有一次换手机会,该换手机会必然应用在「对必败局面进行转移」才有意义,因此只有 s = 1 和 s = 2 的石子数量差大于 2,A 的先手优势不会因为存在换手机会而被转移:
-
两者数量差不超过 2:此时 B 可以利用「对方凑成 3 的倍数必败」规则和「优先使用 s = 0 石子」权利来进入确保自己为必胜态:
-
举个例子,当 s = 1 和 s = 2 的石子数量相等,虽然有 s = 0 的石子,A 先手,但是 A 的首个回合必然不能选 s = 0,否则马上失败结束,因此 A 只能选 s = 1 或 s = 2,此时 B直接选择 s = 0 的石子,交由给 A 的局面 x 没有发生改变,A 只能选择与首个回合相同的 s 游戏才能继续,因此局面会变为「B 先手、s = 1 和 s = 2 的石子数量差为 2」,游戏继续,最终 A 会先遇到「只能凑成 3 的倍数」的局面,即 B 必胜。
-
两者数量差不超过 2:此时无论 A 选择数量较少或较多的 s,B 都在第二回合马上使用 s = 0 的石子进行换手,A
只能继续选与第一回合相同类型的的石子,游戏才能进行,最终 A 会先遇到「只能凑成 3 的倍数」或「石子被取完」的局面,即 B
必胜。
-
-
两者数量差超过 2 :此时即使 A 只要确保第一次选择数量较多的 s,不管 B 是否使用「优先使用 s = 0」的石子,A 都有足够次数数量多 s 来抵消换手(或是在 B 放弃使用 s = 0 之后马上使用),最终都是 B 最先遇到「只能凑成 3 的倍数」的局面,即 A 获胜。
代码如下:
public boolean stoneGameIX(int[] stones) {
int[] cnt = new int[3];
for (int stone : stones){
cnt[stone%3]++;
}
if (cnt[0]%2==0){
if (cnt[1] == 0 || cnt[2] == 0){
return false;
}else{
return true;
}
}else{
if (cnt[1] - cnt[2] > 2 || cnt[1] - cnt[2] < -2){
return false;
}else{
return true;
}
}
}
执行结果:成功