题目传送门
Solution
题意
给定一个长度为偶数 ($n < 20000$)、均为小写字母的字符串,Alice 和 Bob 轮流从中取一个字母,可以选择从左面取一个,也可以选择从右面取,追加到自己已有的字符串末尾(两人初始 r 字符串为空)。字符串被取空后,两个自己的字符串字典序小的胜利。
思路
设字符串长度为 $n$,我们使用 $dp [l][r]$ 表示当字符串剩余 $s [l \dots r-1]$ (不含 $r$)时胜负情况,则 $dp [0][n]$ 即为答案。
我们使用 $dp [l][r] < 0$ 表示 Alice 胜, $dp [l][r] = 0$ 表示平局, $dp [l][r] > 0$ 表示 Bob 胜。
易得,$dp [i][i] = 0$。 我们要想求 $dp [l][r]$,则要知道其更小范围(少两个字母)的 $dp$ 数据,即 $dp [l+2][r]$、$dp [l+1][r-1]$、$dp [l+2][r]$ 的值。根据这三个更小范围的 $dp$ 值,及 Alice、Bob 拿的两个字母的大小,我们可以确定出 $dp [l][r]$ 的值。
代码
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 81 82 83
| #include <bits/stdc++.h>
using namespace std;
const int MAXN = 2010; const int INF = 0x3f3f3f3f;
int T; string s;
int dp[MAXN][MAXN];
int cmp(char a, char b) { return a-b; }
int main() { cin >> T;
while(T--) { memset(dp, 0, sizeof(dp));
cin >> s; int n = s.length(); for(int len=2; len<=n; len+=2) { for(int l=0; l<=n-len; l++) { int r = l+len; dp[l][r] = INF;
{ int res = -INF;
if(dp[l+1][r-1] == 0) res = max(res, cmp(s[l], s[r-1])); else res = max(res, dp[l+1][r-1]);
if(dp[l+2][r] == 0) res = max(res, cmp(s[l], s[l+1])); else res = max(res, dp[l+2][r]); dp[l][r] = min(dp[l][r], res); }
{ int res = -INF;
if(dp[l+1][r-1] == 0) res = max(res, cmp(s[r-1], s[l])); else res = max(res, dp[l+1][r-1]); if(dp[l][r-2] == 0) res = max(res, cmp(s[r-1], s[r-2])); dp[l][r] = min(dp[l][r], res); } } }
if(dp[0][n] < 0) cout << "Alice\n"; else if(dp[0][n] == 0) cout << "Draw\n"; else cout << "Bob\n"; } return 0; }
|
感悟
这种题目,我们可以较为轻松的知道小范围的答案(如两个字母),这样我们就可以再转移到下一个状态(四个字母),如此扩下去。以长度做为 dp 的最外层循环,一步步推导出答案。