[博弈论]取石子游戏
在研究过 Nim 游戏及各种变种之后,Orez 又发现了一种全新的取石子游戏,这个游戏是这样的:
有 n 堆石子,将这 n 堆石子摆成一排。
游戏由两个人进行,两人轮流操作,每次操作者都可以从最左或最右的一堆中取出若干颗石子,可以将那一堆全部取掉,但不能不取,不能操作的人就输了。
Orez 问:对于任意给出的一个初始局面,是否存在先手必胜策略。
输入格式
第一行为一个整数 T,表示有 T 组测试数据。
对于每组测试数据,第一行为一个整数 n,表示有 n 堆石子,第二行为 n 个整数 ai ,依次表示每堆石子的数目。
输出格式
对于每组测试数据仅输出一个整数 0 或 1,占一行。
其中 1 表示有先手必胜策略,0 表示没有。
思路:
太难想了
定义 l [ i , j ] 代表 在第 [ i , j ] 堆石子的左边放多少石子,先手必败
定义 r [ i , j ] 代表 在第 [ i , j ] 堆石子的右边放多少石子,先手必败
因为这两者对称,所以我们先只讨论 l [ i , j ] 的情况。
考虑 l [ i , j ]
一定存在吗? 存在,每一步都可以通过递推推出来。
唯一吗?
唯一,反证法:
假设有两个l [ i , j ] 都使先手必败,大的那个转移到小的那个时,必败 ->必败
冲突,大的那个转移成小的那个,则大的那个为必胜态。
所以一定唯一。
假设第i堆到第j堆已经固定了。
? [ i , j - 1] X
X 代表第 j 堆 有几个石子。
? 即 l [ i , j ]。
定义
在 左边放 L 时, L [ i , j - 1] 必败 这里的 L 其实就 = l [ i , j -1]
在 右边放 R 时,[ i , j - 1] R 必败 这里的 R 其实就 = r[ i , j - 1]
分类讨论:
① 如果 X 正好等于 R,则本身就已经必败了,在左边放0个就好了
② 如果 X < L && X < R, 则 在左边也放 X 个,两边都是X个,先手拿多少,后手在另一边拿多少,所以肯定是后手拿最后一次。当后手拿最后一次时,另一边肯定已经是0了,而不管最后剩下的是多少,留给后手的局面肯定不是L,也不是R,因为X比这两个都小。留给后手的是不是必败,所以先手必败。
③ 如果 L > R,推出 R < X <= L ,则在最左边放 X - 1 个石子的时候先手必败。
当先手拿右边时,
-
首先,先手一定不能把右边取到R
若先手把右边取到R,后手把左边取完。则先手必败 -
如果先手把右边取到x<R时, 后手立即把左边取到和右边相同
–转化为情况②–先手必败 -
如果先手把右边取到x>R时,后手立即把左边取到x−1
由于右边 >R 则右边x最小取到1 左边x最小取到0
则必然会将右边取到R(情况①)或者R以下(情况②) – 必败
当先手拿左边时,
-
当先手把左边取到>=R,后手就把右边取到比左边多1保持情况③
-
当先手把左边取到<R,后手就右边取到和左边相等 保持情况②
④ 如果L < R,推出L<=X<R,则在左边放X+1个石子时,先手必败
当先手拿左边时,
-
如果先手把左边取到>=L+1,则后手就把右边取到>=L,保持情况④
-
如果先手把左边取到L,则后手把右边取到0
-
如果先手把左边取到<L,则后手保持左右两边相等,情况②
当先手拿右边时,
-
当先手把右边取到>=L时,则后手把左边取到比右边多1 保持情况④
-
当先手把右边取到<L时,则后手保持两边相等,情况②
⑤ 如果 X > R && X > L 时,左边放X个和右边一样多,先手必败
L>R时
先手取完后 >L 后手保证左右两边相同
一旦先手把某一边个数取到(R,L]后手保证左边比右边少一个(情况③)
一旦先手把某一边个数取到[R,L) 后手保证右边比左边多一个
一旦先手把某一边个数取到(,R) 后手保证右边和左边一样多
R>L时
对称
先手取完后>R 后手保证左右两边相同
一旦先手把某一边个数取到(L,R]后手保证右边比左边少一个(情况④)
一旦先手把某一边个数取到[L,R)后手保证左边比右边多一个
一旦先手把某一边个数取到(,L)后手保证右边和左边一样多
区间dp,先枚举长度,后枚举左右端点,大范围依赖小范围,
当长度为1时,l[i][j]=r[i][j]=a[i]==a[j] 在左边放一样多就可以使先手必败
const int N = 1010;
int l[N][N],r[N][N];
int t,n;
int a[N];
int main(){
scanf("%d",&t);
while(t--){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int len = 1;len <= n;len ++){
for(int i=1;i+len-1<=n;i++){
int j = i + len - 1;
int L = l[i][j-1], R = r[i][j-1], X = a[j];
if(len == 1) l[i][j] = r[i][j] = a[i];
else{
if(X == R) l[i][j] = 0;//①
//②⑤都是X,所以一起写
else if(X < L && X < R || X > L && X > R) l[i][j] = X;
else if(L > R) l[i][j] = X-1;//③
else l[i][j] = X+1;//④
}
L = l[i+1][j],R = r[i+1][j],X = a[i];
if(len == 1) l[i][j] = r[i][j] = a[j];
else{
if(X == L) r[i][j] = 0;
else if(X < L && X < R || X > L && X > R) r[i][j] = X;
else if(L < R) r[i][j] = X-1;
else r[i][j] = X+1;
}
}
}
printf("%d\n",l[2][n] != a[1]);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探