[AGC048D]Pocky Game

题目

传送门 to AtCoder

思路

通过手玩,我感受到了一个结论:每次要么拿走一个石子,要么拿走一整堆石子。二者分别用于 “挨时间” 和 “推进度”。但是我并没有给出证明。而这证明,就是最重要的性质:石子数越多,胜率越高

为什么呢?因为石子数变多之后,仍然可以采取相同的策略;只有最后一次,统一成 “拿完整堆石子”。所以一定严格更优。

应当说,这是博弈常见操作:求一个 “分界点”,之前都是 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;
}
上一篇:[LeetCode] 390. Elimination Game


下一篇:libGDX游戏开发之画面场景和画面自适应(二)