您的位置:首页 > 其它

【BZOJ4600】硬币游戏,博弈

2016-09-18 18:52 316 查看
传送门

思路:

考场爆零系列

因为当时博弈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

枚举某一后继状态x′i,其中x′i中要翻过来的硬币是x1,x2,x3,..xn

(显然这些硬币都是≤x的)

我们可以把这个状态表示为x′i={x1,x2,x3,..xn}

那么这又是最上面我们所说的拆分子状态了

即x′i={x1}∪{x2}∪{x3}∪..∪{xn}

sg[x′i]=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");
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: