题目
思路
通过手玩,我感受到了一个结论:每次要么拿走一个石子,要么拿走一整堆石子。二者分别用于 “挨时间” 和 “推进度”。但是我并没有给出证明。而这证明,就是最重要的性质:石子数越多,胜率越高。
为什么呢?因为石子数变多之后,仍然可以采取相同的策略;只有最后一次,统一成 “拿完整堆石子”。所以一定严格更优。
应当说,这是博弈常见操作:求一个 “分界点”,之前都是 N N N 状态,之后都是 P P P 状态。
接下来我们就可以考虑游戏进程了:双方都在 “挨时间” 的过程不重要,因为终究有一个人会 “挨不赢”(比如,不得已拿完自己所在的那一堆石子)。所以我们只需要考虑 “推进度” 时的局面,比如左手玩家刚拿完一整堆石子——此时剩下的石子堆是 [ l , r ] [l,r] [l,r],但是最右边的石子是不完整的。怎么判断?
仍然利用上面的性质:一定存在 v v v 使得最右边的石子堆至少是 v v v 个时,右手玩家(先手)必胜。所以我们只需要求这个,记为 R l , r R_{l,r} Rl,r 。类似的,我们还需要求一个 L l , r L_{l,r} Ll,r 。
怎样转移呢?以 L l , r L_{l,r} Ll,r 为例。假如拿完这一堆就赢,即 G l + 1 , r > a r G_{l+1,r}>a_r Gl+1,r>ar,那么 L l , r = 1 L_{l,r}=1 Ll,r=1 。否则,一定会进入拉锯战:双方都一个一个拿。那么,当先手这堆石子的数量小于 L l , r − 1 L_{l,r-1} Ll,r−1 时,后手拿完就赢了;当后手这堆石子的数量小于 G l + 1 , r G_{l+1,r} Gl+1,r 时,先手拿完就赢了。显然共需要拿 ( a r − G l + 1 , r + 1 ) (a_r-G_{l+1,r}+1) (ar−Gl+1,r+1) 轮,由于此时后手还无法取胜,所以先手最初至少有 L l , r − 1 + ( a r − G l + 1 , r + 1 ) L_{l,r-1}+(a_r-G_{l+1,r}+1) Ll,r−1+(ar−Gl+1,r+1) 个石子,这就是 L l , r L_{l,r} Ll,r 的值。
R l , r R_{l,r} Rl,r 的计算同理。时间复杂度 O ( T n 2 ) \mathcal O(Tn^2) O(Tn2) 。
代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cctype>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar())
if(c == '-') f = -f;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MAXN = 105;
llong l[MAXN][MAXN], r[MAXN][MAXN];
int a[MAXN];
int main(){
for(int T=readint(); T; --T){
int n = readint();
rep(i,1,n){
a[i] = readint();
l[i][i] = r[i][i] = 1;
}
drep(i,n-1,1) rep(j,i+1,n){
if(r[i+1][j] > a[j]) l[i][j] = 1;
else l[i][j] = a[j]+l[i][j-1]-r[i+1][j]+1;
if(l[i][j-1] > a[i]) r[i][j] = 1;
else r[i][j] = a[i]+r[i+1][j]-l[i][j-1]+1;
}
puts(l[1][n] <= a[1] ? "First" : "Second");
}
return 0;
}