【BZOJ4600】硬币游戏,博弈


传送门
思路:
考场爆零系列
因为当时博弈pi都不会,连SG函数是啥都不知道
这里写图片描述
现在会了一点点博弈,来看一下
很快写出了10分(n<=16)做法……
orz Va爷的题解
感觉Va爷的博客思路讲的不是很清楚,昨天晚上想了很久,感觉有一个比较合适的解释
我们发现初始状态是可以分解成若干只有一个是反面朝上,即 ai=1 的状态
举例来说,对于样例
1001000010001011
这个状态的子状态就是 1 ,0001, 000000001 , 0000000000001 , 0000000000001 , 000000000000001 0000000000000001
类似分治子问题的思想
也就是说,如果我们当前从左向右扫到第i枚硬币为1(反面朝上),那么前面1~i-1的硬币都是0(正面朝上)
显然对[1,i]的硬币进行操作是对>i的硬币没有影响的
那我们把问题变成求”[1,i]区间中a[i]=1,其余为0”状态下的SG函数,状态表示为 sg[i]
做完这个子状态后再继续往后扫,扫到j
我们就可以看成前面那个i的子状态已经解决了,那么状态肯定是”[1,j]区间中a[j]=1,其余为0”
这和上面是同一个问题
初始状态的SG函数就是这些子状态的异或和
那怎么求这些子状态的SG函数?
按照题目给出的规则暴力枚举p,q就可以了
比如说我们正在处理状态 x
枚举某一后继状态xi,其中 xi 中要翻过来的硬币是 x1,x2,x3,..xn
(显然这些硬币都是 x 的)
我们可以把这个状态表示为 xi ={ x1,x2,x3,..xn }
那么这又是最上面我们所说的拆分子状态了
xi ={ x1 }∪{ x2 }∪{ x3 }∪..∪{ xn }
sg[xi] = sg[x1] xor sg[x2] xor .. xor sg[xn]
枚举出x的所有后继状态,求出mex(最小未出现自然数)就是sg[x]啦
这里给出一个结论(仅面向初学者)

一个状态的SG=它的所有后继状态的SG的mex=它的所有子状态的SG的异或和

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm> 
using namespace std;
int T,n,maxn;
int sg[21][30005],a[30005];
bool b[55];
void dp(int y)
{
    if (sg[maxn][y]!=-1) return;
    memset(b,0,sizeof(b));
    int ta=0,tb=0,tc=y,tt,ty;
    for (;tc%2==0;tc/=2) ++ta;
    for (;tc%3==0;tc/=3) ++tb;
    for (int q=1;q<=maxn;++q)
        for (int p=1;p*q<=ta;++p)
        {
            tt=y;ty=-1;
            for (int j=0;j<=q;++j)
            {
                if (tt==y) ty=0;
                else ty=(ty==-1?sg[maxn][tt]:ty^sg[maxn][tt]);
                for (int k=1;k<=p;++k) tt/=2;
            }
            if (ty!=-1) b[ty]=1;
        }
    for (int q=1;q<=maxn;++q)
        for (int p=1;p*q<=tb;++p)
        {
            tt=y;ty=-1;
            for (int j=0;j<=q;++j)
            {
                if (tt==y) ty=0;
                else ty=(ty==-1?sg[maxn][tt]:ty^sg[maxn][tt]);
                for (int k=1;k<=p;++k) tt/=3;
            }
            if (ty!=-1) b[ty]=1;
        }
    for (int i=0;i<55;++i)
        if (!b[i]) return void(sg[maxn][y]=i);
}
main()
{
    memset(sg,-1,sizeof(sg));
    for (scanf("%d",&T);T;--T)
    {       
        scanf("%d%d",&n,&maxn);
        int ans=0;
        for (int i=1;i<=n;++i) dp(i);
        for (int i=1;i<=n;++i)
        {
            scanf("%d",a+i);
            if (!a[i]) ans^=sg[maxn][i];
        }
        if (ans) puts("win");
        else puts("lose");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值