博弈论——《取石子》《取石子游戏》

传送门:活动 - AcWing

思路:

结论:在所有堆的石子个数>1的情况下

只要石子数+石子堆数-1==b是奇数,那么先手必胜。b是不计算所有个数为1的石子堆得出的。

b是奇数的情况下一定存在一个偶数后继,是偶数的情况下所有的后继都一定是奇数后继。

定义终局局面:只有一堆并且石子的个数是奇数,这样子到最后先手留给后手的是一个偶数石子个数局面,此时后手必败。

在存在石子个数为1的堆的情况下:

定义一个二元状态f(a,b),a表示当前个数为1的石子堆的数量,b表示个数不为1的所有石子堆的个数加上石子的堆d数减1的值。

现在有一个f(a,b)状态,在这两部分当中有五种操作:

1.从a中取一个,变成f(a-1,b)

2.从b中取一个,变成f(a,b-1)

3.合并b中的2个,变成f(a,b-1)

4.合并a中的2个,变成f(a-2,b+3)

5.合并a中的1个和b中的一个,变成f(a-1,b+1)
代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<unordered_set>
using namespace std;
const int N=60,M=5e4+10;
typedef long long ll;
int n,m,k;

int f[N][M];

int dp(int a,int b)
{
    if(f[a][b]!=-1) return f[a][b];
    if(!a) return f[a][b]=b%2;
    if(b==1) return dp(a+1,0);

    if(a&&!dp(a-1,b)) return f[a][b]=1;
    if(b&&!dp(a,b-1)) return f[a][b]=1;
    if(a>=2&&!dp(a-2,b+(b?3:2))) return f[a][b]=1;
    if(a&&b&&!dp(a-1,b+1)) return f[a][b]=1;

    return f[a][b]=0;
}
int main()
{
    int t;
    scanf("%d",&t);
    memset(f,-1,sizeof f);
    while(t--)
    {
        int n;
        scanf("%d",&n);
        int a=0,b=0;
        for(int i=0;i<n;i++)
        {
            int x;
            scanf("%d",&x);
            if(x==1) a++;
            else{
                if(b==0) b+=x;
                else b+=x+1;
            }
        }
    //    printf("%d %d\n",a,b);

        if(dp(a,b)) puts("YES");
        else puts("NO");
    }
    return 0;
}

传送门:取石子游戏

思路:

设left[i][j]为在左边放多少个石子,先手必败,设right[i][j]为在右边放多少个石子,先手必败。

对于left[i][j]是一定存在且唯一的。

证明,假如现在存在两个数l1和l2且l1>l2,使得左边分别放l1,l2都是先手必败的话,如果先手取得使l1变成l2,那么留给后手的就业是一个必败态,但这是不可能的,必败态无法转移到另一个必败态,所以证明必败态唯一。

设有上图一个[i,j]的区间,X为当前位置j的取值,L为left[i][j-1]的值,R为right[i][j-1]的值。

对于i-1的取值有如下几种情况:

1. 若R==X,此时left[i][j]=0;

2.若X<L,X<R  ,此时left[i][j]=x; 对于先手在哪边取了多少个,后手都在另一边取相同个数的石子,那么在最后一定有某一边已经变成了0个石子,而另一边还有石子这种情况。因为每次给后手留下的都不是必败态,所以一定是留下必胜态,故先手必败。

3.若L>R,R<X<=L, 此时取left[i][j]=x-1,先手必败 ,先手不能把右边取到R或小于R,否则后手可以把左边取光或者取到和右边相等,分别变成情况1和情况2。若先手把左边取到>=R,后手都可以把右边取到比左边+1,如果先手把左边取到<R的情况,后手就可以把右边取到和左边相同的数。也是先手必败。

若L<R,L<X<=R,此时left[i][j]=X+1,其余情况类似。

4.若X>L且X>R,left[i][j],left[i][j]=X , 不管先手怎么取,后手都在另一边做相同操作,当某一次先手取完后满足上面三种情况之一后手就按照上述的情况来取,这样子先手也必败。

同时,上面的这些在右边也是个对称的操作。

代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
const int N=1e3+10,M=5e4+10;
int n;
int a[N];
int l[N][N],r[N][N];
int main()
{
    int t;
    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;
            if(len==1) l[i][j]=r[i][j]=a[i];
            else
            {
                int L=l[i][j-1],R=r[i][j-1],X=a[j];
                if(R==X) l[i][j]=0;
                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(L==X) r[i][j]=0;
                else if(X<L&&X<R||X>L&&X>R) r[i][j]=X;
                else if(R>L) r[i][j]=X-1;
                else r[i][j]=X+1;
            }
        }
        if(n==1) puts("1");
        else printf("%d\n",l[2][n]!=a[1]);


    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值