Atcoder Grand Contest 048 (AGC048) D

原文链接www.cnblogs.com/zhouzhendong/AGC048D.html

前言

我居然更博了???

题解

结论1

对于任何一方来说,他当前要取的那堆的石子个数多多益善。

证明1

假设多了 (k) 个石子,那么对于在原先的石子堆中取石子的任意方案,只需要在第一步多取 (k),就对应到新石子堆的一个方案。由此可见新石子堆的决策集合包含了旧石子堆,所以必然不劣。

结论2

取石子时,只可能取走一个 或者 取走一堆。

证明2

假设没取完一堆,但是取走了大于一个,那不如取一个,因为取一个剩下的石子数较多,肯定不劣。


如果以事件"某一次某一方取走一堆"为双方最优决策序列的分隔点,那么我们会发现双方一直都在轮流取一个石子。

这启发我们使用动态规划解决这个问题。

(vl(L,R)) 表示保留 ([L,R]) 范围的石子,如果左方先手,且在第 (L) 堆石子之前还有一堆大小为 (x(x > 0)) 的石子,那么 (x) 至少为多少才能保证先手必胜。

类似地,我们定义 (vr(L,R)),表示右方先手,在右侧至少有一堆多大的石子才能保证必胜。

两部分做法一样,所以这里我们不妨只考虑左方。

左方第一步有两种决策:拿光一堆、拿一个。

(vr(L,R-1) > a[R]) 时,显然直接拿光就赢了,所以此时 (lv(L,R) = a[R]+1)

否则肯定是双方轮流拿一个,直到某一方发现这一次如果拿光的话对方就输了。设左侧石子堆大小为 (x),则左方在拿走 (x - vr(L,R-1) + 1) 个石子后,右方拿一堆,右方获胜;或者右方在拿走 (a[R] - vl(L,R-1) + 1) 个石子后,左方拿一堆,左方获胜。所以左方获胜的条件时 (x - vr(L,R-1) + 1 > a[R] - vl(L,R-1) + 1),即 (vr(L,R) = min(x) = a[R] - vl(L,R-1) + vr(L,R-1) + 1)
r
直接 DP 或者 记忆化 dfs 均可。

于是我们得到了一个单组数据 (O(n^2)) 的算法,解决了此题。

代码

#include <bits/stdc++.h>
#define clr(x) memset(x,0,sizeof(x))
#define For(i,a,b) for (int i=(a);i<=(b);i++)
using namespace std;
typedef long long LL;
LL read(){
	LL x=0,f=0;
	char ch=getchar();
	while (!isdigit(ch))
		f=ch=='-',ch=getchar();
	while (isdigit(ch))
		x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return f?-x:x;
}
const int N=105;
int n;
int a[N];
LL l[N][N],r[N][N];
LL vr(int L,int R);
LL vl(int L,int R){
	if (L>R)
		return 1;
	LL &res=l[L][R];
	LL v=a[R];
	if (res)
		return res;
	LL b=vr(L,R-1);
	if (b>v)
		return res=1;
	LL a=vl(L,R-1);
	// x - a + 1 > v - b + 1
	return res=max(1LL,v-b+1+a-1+1);
}
LL vr(int L,int R){
	if (L>R)
		return 1;
	LL &res=r[L][R];
	LL v=a[L];
	if (res)
		return res;
	LL b=vl(L+1,R);
	if (b>v)
		return res=1;
	LL a=vr(L+1,R);
	// x - a + 1 > v - b + 1
	return res=max(1LL,v-b+1+a-1+1);
}
void solve(){
	n=read();
	For(i,1,n)
		a[i]=read();
	clr(l),clr(r);
	puts(vl(2,n)<=a[1]?"First":"Second");
}
int main(){
	int T=read();
	while (T--)
		solve();
	return 0;
}
原文地址:https://www.cnblogs.com/zhouzhendong/p/AGC048D.html