ACM/ICPC 2011 成都现场赛 Alice and Bob 题解
这种博弈问题我做得很少,开始在纸上推,还是比较复杂。看了别人的题解才大概知道应该怎么做。大体上是把问题等价地简化,然后就有点类似动态规划了:先把边界搞清楚,然后记忆化递归看是必胜还是必败。
从这道题的规则上看,每次操作要么让一组数减 1, 要么把两组数合并成一组。其实还有一个特别的地方,那就是,如果有某组数是 1, 如果有一方让这组数减 1, 这组数就被抹掉了,组数同时也少了 1. 所以如果有一组数是 1 的话,将其减 1 的操作,相当于平时操作了两下,就改变了奇偶性。
我们可以发现,如果玩家面临的局面,剩下的可行操作次数是偶数次,那么他必败。如果有 n 组数,并且每组都大于 1, 必胜的玩家可以保证可行操作的奇偶性不改变,因为对方没有办法遇到某组数为 1(只有这样才能改变奇偶性)。但是如果有若干组的数字是 1, 那就复杂了。
所以状态的描述 dp[i][j] 表示,有 i 组的数字是 1, 且其它组(即数字不为 1 的组)的“可操作数”为 j 的时候,先手是必胜还是必败。其中“可操作数”的计算:当没有其它数字的组的时候,可操作数为 0; 当只有一个组时,可操作数为那个组的数字;当有 n > 1 个组时,可操作数为 (S + n – 1), 其中 S 是那些组的数字之和。
状态转移过程中的重点,是把两个为 1 的组合并的时候,会产生一个大于 1 的组。这会根据“可操作数”的不同而产生两种情况,具体在代码的 33 到 39 行找。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | #include <cstring> #include <cstdio> using namespace std; typedef signed char int8; const int8 UNKNOWN = -1; const int8 FIRST_LOSE = 0; const int8 FIRST_WIN = 1; const int ONE = 55; const int SUM = 55555; int8 dyn[ONE][SUM]; int8 dp(int one, int sum) { if (sum == 1) ++one, sum = 0; if (dyn[one][sum] != UNKNOWN) return dyn[one][sum]; dyn[one][sum] = FIRST_LOSE; if (one > 0 && dp(one - 1, sum) == FIRST_LOSE) dyn[one][sum] = FIRST_WIN; // Erase a '1'. else if (one > 0 && sum > 0 && dp(one - 1, sum + 1) == FIRST_LOSE) dyn[one][sum] = FIRST_WIN; // Merge a '1' with others. else if (sum > 0 && dp(one, sum - 1) == FIRST_LOSE) dyn[one][sum] = FIRST_WIN; // Decrease others. else if (one > 1) { if (sum == 0 && dp(one - 2, 2) == FIRST_LOSE) dyn[one][sum] = FIRST_WIN; // Merge two '1's and no other numbers exist. else if (sum > 0 && dp(one - 2, sum + 3) == FIRST_LOSE) dyn[one][sum] = FIRST_WIN; // Merge two '1's and other numbers exist. } return dyn[one][sum]; } inline void input(int &one, int &sum) { one = 0; sum = -1; int t, n; scanf("%d", &n); while (n--) { scanf("%d", &t); if (t == 1) ++one; else sum += t + 1; } if (sum < 0) sum = 0; } inline void init() { memset(dyn, -1, sizeof dyn); for (int i = 0; i < SUM; ++i)\ if (i & 1) dyn[0][i] = FIRST_WIN; else dyn[0][i] = FIRST_LOSE; } int main() { // freopen("input.txt", "r", stdin); int T, one, sum; scanf("%d", &T); init(); for (int i = 1; i <= T; ++i) { input(one, sum); if (dp(one, sum) == FIRST_WIN) printf("Case #%d: Alice\n", i); else printf("Case #%d: Bob\n", i); } return 0; } |
