Atcoder Grand Contest 048 (AGC048) D - Pocky Game 博弈,动态规划
原文链接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\)。
直接 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;
}