zoukankan      html  css  js  c++  java
  • 【BZOJ1413】[ZJOI2009]取石子游戏(博弈论,动态规划)

    【BZOJ1413】[ZJOI2009]取石子游戏(博弈论,动态规划)

    题面

    BZOJ
    洛谷

    题解

    神仙题.jpg。(ZJOI)是真的神仙。
    发现(SG)函数等东西完全找不到规律,无奈只能翻题解。

    首先设(L[i][j])表示在([i,j])这一段区间的左侧放上一堆数量为(L[i][j])的石子后,先手必败。同理定义(R[i][j])表示右侧。
    首先我们可以证明(L[i][j])唯一,假设存在两个(L[i][j]),显然较大的那个可以通过一步转移转移到较小的那个,所以不合法。因此(L[i][j])唯一。
    接下来考虑如何证明(L[i][j])一定存在。假设(L[i][j])不存在,那么对于这段区间而言,在左边加上任意一堆石子先手都必胜,既然先手必胜意味着先手进行一步操作之后可以到达一个必败态,这里分情况讨论。假设先手拿的是最左边的一堆石子,因为不存在(L[i][j]),所以只要拿了左边的石子之后,当前局面都是必胜态,所以不可能拿左边的石子。那么只能拿右边的石子,那么无论右边拿了一定量之后,无论左边添加了多少,都是一个必败态,那么此时后手在左侧随便拿走一定数量,这个状态也还是一个必败态,显然也不成立。因此(L[i][j])必定存在。
    综上,我们知道了(L[i][j])一定存在并且唯一,而(L[][],R[][])显然是对称的,因此(R[i][j])也满足上述性质。
    现在考虑如何求解(L[i][j]),(R[i][j])同理。首先边界情况显然,(L[i][i]=a[i]),因为只剩下两堆一模一样的情况的时候,后手只需要模仿先手的行动对称执行就好了,这样子一定不会输,即先手必败。
    接下来来大力分类讨论,为了方便(为了抄),设(L=L[i][j-1],R=R[i][j-1],x=a[j])

    • (x=R)
      这种情况下显然只需要直接把(a[j])放进去就好了,即这个区间本身就是一个必败态。所以(L[i][j]=0)
    • (x<L,x<R)
      这种情况下(L[i][j]=x)。这种情况下最靠左的(L[i][j])(x=a[j])是相同的,意味着先手无论怎么取,后手显然可以学着它的方法取,也就意味着左右两堆中显然必然会先拿完一堆,此时后手学着拿的那一堆的石子数一定也是小于(L,R)的。假设先手先拿完了最靠右的一堆,即剩下了([i,j-1]),因为(L[i][j-1])表示的是在这一段区间最左侧加入一个(L[i][j-1])的堆,无论先手怎么取先手都是必败的,那么我们等价的认为先手取走了这一堆的一部分,显然后手是必胜的。假如先手先取完的是最左的一堆,同理,(R[i][j-1])的含义是在最右侧加入了一堆,而(a[j]<R[i][j-1]),我们还是可以等价的认为先手在这一堆中取走了若干石子,而这个状态对于先手而言是必败状态,因此显然后手必胜。
    • (R<x<L)
      这种情况下(L[i][j]=x-1)。这样子考虑,假设先手先拿了左边这一堆,那么假设还剩下了(z)个石子,如果(z<R),后手把右侧的那一堆也给拿成(z)就变成了上面的情况。如果(zge R),那么后手把最后那一堆拿成(z+1),于是又回到了这种情况,相当于这种情况递归处理。如果先手先拿的是右侧的这一堆,还是一样的,假设把它拿成了(z),如果(z<R),同上可以变成(x<L,x<R)的情况;如果(y=R),直接把左边拿完,就变成了(R[i][j-1])的定义了,先手必败;如果(z>R),把左边那堆变成(z-1),同样递归处理。
    • (L<x<R)
      分析同上,(L[i][j]=x+1)
    • (x>L,x>R)
      (L[i][j]=x)。还是一样的,假设先手把其中一堆拿成了(z)。如果(z>L,R),跟着先手拿成一样多的石子则又回到了这种情况。如果(z<L,R),则可以回到情况(x<L,R)。否则的话对应着把另外一堆变成(z+1)或者(z-1),对应着(L<x<R)(R<x<L)两种情况。

    (R[][])(L[][])是对称的,类似的求解即可。
    那么最终只需要判断(L[2][n])(a[1])是否相等即可判断胜负情况。
    代码有点丑

    #include<iostream>
    #include<cstdio>
    using namespace std;
    #define MAX 1010
    inline int read()
    {
    	int x=0;bool t=false;char ch=getchar();
    	while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    	if(ch=='-')t=true,ch=getchar();
    	while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    	return t?-x:x;
    }
    int n,a[MAX],L[MAX][MAX],R[MAX][MAX];
    int main()
    {
    	int T=read();
    	while(T--)
    	{
    		n=read();
    		for(int i=1;i<=n;++i)a[i]=read();
    		for(int i=1;i<=n;++i)L[i][i]=R[i][i]=a[i];
    		for(int len=2;len<=n;++len)
    			for(int i=1,j=i+len-1;j<=n;++i,++j)
    			{
    				int x=a[j],l=L[i][j-1],r=R[i][j-1];
    				if(x==r)L[i][j]=0;
    				else if((x>l&&x>r)||(x<l&&x<r))L[i][j]=x;
    				else if(r<x&&x<l)L[i][j]=x-1;
    				else L[i][j]=x+1;
    				x=a[i],l=L[i+1][j],r=R[i+1][j];
    				if(x==l)R[i][j]=0;
    				else if((x>l&&x>r)||(x<l&&x<r))R[i][j]=x;
    				else if(r<x&&x<l)R[i][j]=x+1;
    				else R[i][j]=x-1;
    			}
    		puts(a[1]==L[2][n]?"0":"1");
    	}
    }
    
  • 相关阅读:
    各种数据库默认端口总结
    Entity Framework学习
    Entity Framework学习
    .Net MVC API初试
    MongoDB Shell
    MongoDB安装及简单实验
    Android Studio记录
    Android使用Fragment程序崩溃
    git操作笔记
    centos防火墙设置
  • 原文地址:https://www.cnblogs.com/cjyyb/p/9756449.html
Copyright © 2011-2022 走看看