题解:P16531 [THUPC 2026 决赛] 棋盘对弈游戏
lailai0916 · · 题解
题意简述
两枚棋子从位置
双方都采用最优策略,求游戏结束时先手得分减去后手得分。
解题思路
任意时刻,把位置靠右的棋子称为前棋,另一枚称为后棋。棋子只会向右移动,所以后棋左侧的格子以后不可能再到达。前棋右侧的格子则一定尚未占据。状态只需额外记录两枚棋子之间的占据情况。
先处理两枚棋子相距较远的情况。设前棋位于
前棋可以每次移动一格,依次取得
反过来,从后棋之后最靠左的可达格子开始考虑。由于每次移动不超过
所有分值都为正。任何一方跳过自己能够依次取得的格子,都只会永久放弃正收益,也不能侵入另一侧。因此,这个局面的后续分差已经确定。若区间占据掩码为
其余需要决策的状态满足
定义
若当前棋子跳到
枚举所有合法落点并取最大值即可。下面分别说明掩码变化。
前棋从
后棋跳到仍在
若后棋跳过前棋,到达
若当前玩家无合法落点,本回合直接跳过,状态参数不变,只需交换行动者并将结果取负。当
距离
所有需要记忆化的状态都满足
代码按前棋位置、后棋位置从右到左预先调用状态。前棋移动会增大前棋位置。后棋留在左侧时会增大后棋位置,越过前棋时则会增大新的前棋位置。除立即终止的分离状态外,所有依赖都已经计算。因此,递归不会形成长度为
每个局部状态枚举至多
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=100005;
const int S=32;
const ll inf=4000000000000000000LL;
int n,tim;
ll a[N],sum[N],f[2][N][S];
int tag[2][N][S];
bool can(int x,int y,int s)
{
for(int i=y+1;i<=n&&i<=y+4;i++)
{
if(i==x||(s>>(i-y-1)&1))continue;
return 1;
}
return 0;
}
ll dfs(int o,int x,int y,int s)
{
int d=x-y;
if(x==n&&!can(x,y,s))return 0;
if(!o&&d>=6)
{
ll ans=sum[n]-sum[x];
for(int i=y+1;i<x;i++)
{
if(!(s>>(i-y-1)&1))ans-=a[i];
}
return ans;
}
int id=-1;
if(d<6)
{
id=(1<<(d-1))-1+s;
if(tag[o][y][id]==tim)return f[o][y][id];
}
ll ans=-inf;
bool flag=0;
if(!o)
{
for(int i=x+1;i<=n&&i<=x+4;i++)
{
flag=1;
int ns=s|(1<<(d-1));
ans=max(ans,a[i]-dfs(1,i,y,ns));
}
if(!flag)ans=-dfs(1,x,y,s);
}
else
{
for(int i=y+1;i<=n&&i<=y+4;i++)
{
if(i==x||(s>>(i-y-1)&1))continue;
flag=1;
if(i<x)
{
int ns=s>>(i-y);
ans=max(ans,a[i]-dfs(0,x,i,ns));
}
else ans=max(ans,a[i]-dfs(1,i,x,0));
}
if(!flag)ans=-dfs(0,x,y,s);
}
if(d<6)
{
tag[o][y][id]=tim;
f[o][y][id]=ans;
}
return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin>>t;
while(t--)
{
cin>>n;
a[1]=a[2]=0;
sum[0]=sum[1]=sum[2]=0;
for(int i=3;i<=n;i++)
{
cin>>a[i];
sum[i]=sum[i-1]+a[i];
}
tim++;
for(int i=n;i>=2;i--)
{
for(int j=i-1;j>=1&&j>=i-5;j--)
{
for(int k=0;k<(1<<(i-j-1));k++)
{
dfs(0,i,j,k);
dfs(1,i,j,k);
}
}
}
cout<<dfs(1,2,1,0)<<'\n';
}
return 0;
}