跳到主要内容

取石子游戏

· 阅读需 7 分钟

ZJOI是什么神仙出题组,能想到这道题目,不看题解一辈子写不出来


这道题不可做,不看题解连思路都没有(我还是太蒟蒻了)

首先设立状态转移数组L[l][r]L[l][r],表示在[l,r][l,r]的左边放上一段大小为L[l][r]L[l][r]的石子的情况下必败,同理可以定义R[l][r]R[l][r]

然后也可以很快的得到边界情况,就是L[i][i]=R[i][i]=w[i]L[i][i]=R[i][i]=w[i],这个也很好理解对于两堆相同的石子,后手一定可以进行和先手对称的操作

再然后好像大概是没有思路了(我就止步于此),然后呢,很简单(很简单在哪里),暴力分讨就行了

我们在此先分讨L[l][r]L[l][r],R[l][r]R[l][r]也可以同理得到

首先令L=L[l][r−1],R=R[l][r−1],x=w[r]L=L[l][r-1],R=R[l][r-1],x=w[r]


Case1Case1​

R=xR=x时由定义可得此时[l,r][l,r]是必败局面,L[l][r]=0L[l][r]=0


Case2Case2​

x<min(L,R)x\lt min(L,R)时L[l][r]=xL[l][r]=x,即证状态(x,al,al+1...ar−1,x)(x,a_l,a_{l+1}...a_{r-1},x)是必败态

因为左右两端相等,所以后手一定能进行和先手对称的操作,直到产生形如

(al,al+1...ar−1,y)(a_l,a_{l+1}...a_{r-1},y)或(y,al,al+1...ar−1)(y,a_l,a_{l+1}...a_{r-1})的状态

因为0<y≤x<min(L,R)0\lt y\le x\lt min(L,R),由数组定义可得此时L≠R≠yL\not= R\not= y一定是必胜态,即先手必败

注意这里我们规定了x≠0x\not= 0,因此后面如果要直接使用Case2Case2需要满足x≠0x\not= 0这个条件


Case3Case3​

L≤x<RL\le x\lt R时L[l][r]=x+1L[l][r]=x+1,即证状态(x+1,al,al+1...ar−1,x)(x+1,a_l,a_{l+1}...a_{r-1},x)是必败态

这里继续分讨

A.若先手拿左边的那堆,设拿完后还剩zz个

1.若z>Lz\gt L,后手可以将右边那堆拿成z−1z-1个,因为z−1≥L>0z-1\ge L\gt 0,回到Case3Case3本身

2.若z=Lz=L,后手不妨直接将右边那堆拿完,由L[l][r−1]L[l][r-1]定义可知目前是必败局面

3.若0<z<L0\lt z\lt L,后手可以将右边那堆拿成zz个,就回到Case2Case2是必败局面

4.若z=0z=0,此时右边那堆的个数L≤k<RL\le k\lt R,由定义可得目前是必败局面

B.若先手拿右边的那堆,设拿完后还剩zz个

1.若z>Lz\gt L,后手可以将左边那堆拿成z+1z+1个,回到Case3Case3本身

2.若0<z≤L0\lt z\le L,后手可以将最左边那堆拿成zz个,回到Case2Case2的必败局面

3.若z=0z=0,后手可以将最左边那堆拿成LL个,由定义可得目前是必败局面


Case4Case4​

R<x≤LR\lt x\le L时L[l][r]=x−1L[l][r]=x-1,即证状态(x−1,al,al+1...ar−1,x)(x-1,a_l,a_{l+1}...a_{r-1},x)是必败态

证明同Case3Case3

A.若先手拿左边的那堆,设拿完后还剩zz个

1.若z≥Lz\ge L,后手可以将右边那堆拿成z+1z+1个,回到Case4Case4本身

2.若0<z<L0\lt z\lt L,后手可以将最右边那堆拿成zz个,回到Case2Case2的必败局面

3.若z=0z=0,后手可以将最右边那堆拿成RR个,由定义可得目前是必败局面

B.若先手拿右边的那堆,设拿完后还剩zz个

1.若z≥Rz\ge R,后手可以将左边那堆拿成z−1z-1个,回到Case4Case4本身

2.若z=Rz=R,后手不妨直接将左边那堆拿完,由R[l][r−1]R[l][r-1]定义可知目前是必败局面

3.若0<z<R0\lt z\lt R,后手可以将左边那堆拿成zz个,就回到Case2Case2是必败局面

4.若z=0z=0,此时左边那堆的个数0<k<L0\lt k\lt L,由定义可得目前是必败局面


Case5Case5​

x>max(L,R)x\gt max(L,R)时L[l][r]=xL[l][r]=x,即证状态(x,al,al+1...ar−1,x)(x,a_l,a_{l+1}...a_{r-1},x)是必败态

假设先手取了其中一堆变成了zz个

1.若z>max(L,R)z\gt max(L,R),回到Case5Case5本身

2.若0<z<max(L,R)0\lt z\lt max(L,R),将另一堆也拿成zz个便得到Case2Case2的必败局面

3.若z=0z=0,只需要将另一堆拿成LL或RR,由定义可得此时必败

4.若L≤z≤RL\le z\le R或R≤z≤LR\le z\le L

Case3Case3可以解决最左堆L<z≤RL\lt z\le R,最右堆L≤z<RL\le z\lt R的情况

同理Case4Case4可以解决最左堆R≤z<LR\le z\lt L,最右堆R<z≤LR\lt z\le L的情况

所以我们只需解决最左堆是z=Lz=L和最右堆是z=Rz=R的情况

而这两种情况把另一堆拿完就可以由定义得到必败


综上所述,我们得到​

L[l][r]= \begin{cases} 0, &{x=R}\\ x+1, &{L\le x\lt R}\\ x-1, &{R\lt x\le L}\\ x, &otherwise \end{cases}$$ <h4> 同理也可以得到$R[l][r]$的转移方式 而最后解的判断直接由数组定义得到 当且仅当$L[2][n]=w[1]$或$R[1][n-1]=w[n]$时(只用判断一种)时先手必败 </h4> ```c++ /* 其实也挺好想的一道题目,每次的子游戏只有少了最左和最右两种 然后带记忆化 DFS 一下应该就可以解决了 其实也可以类似区间 DP 的思路找吧 私人阴间分讨,ZJOI你无敌了 🐎爆炸了 */ #include<bits/stdc++.h> using namespace std; #define ll long long #define pii pair< int,int > #define QAQ return ; #define QWQ return 0; #define TAT return const int N=1e3+10; int w[N]; int L[N][N] ,R[N][N]; void sovel() { int n; cin>>n; for( int i=1;i<=n;i++ ) for( int j=1;j<=n;j++ ) L[i][j]=R[i][j]=0; for( int i=1;i<=n;i++ ) cin>>w[i], L[i][i]=R[i][i]=w[i]; for( int len=2;len<=n;len++ ) { for( int l=1, r=l+len-1;r<=n;l++, r++ ) { int l1=L[l][r-1], r1=R[l][r-1], x1=w[r]; if( x1 == r1 ) L[l][r]=0; else if( x1 >= l1 && x1 < r1 ) L[l][r]=x1+1; else if( x1 <= l1 && x1 > r1 ) L[l][r]=x1-1; else L[l][r]=x1; int l2=L[l+1][r], r2=R[l+1][r], x2=w[l]; if( x2 == l2 ) R[l][r]=0; else if( x2 >= r2 && x2 < l2 ) R[l][r]=x2+1; else if( x2 <= r2 && x2 > l2 ) R[l][r]=x2-1; else R[l][r]=x2; } } cout<<( L[2][n] == w[1] ? "0\n" : "1\n" ); } int main() { // freopen( "txt.in","r",stdin ); ios::sync_with_stdio( false ); cin.tie( nullptr ), cout.tie( nullptr ); int t; for( cin>>t;t;t-- ) sovel(); QWQ } ```