博弈问题总集第四类----Multi-SG

来源:互联网 发布:淘宝一楼土木人创业店 编辑:程序博客网 时间:2024/05/22 17:18

一堆变多堆!

所谓Multi-SG就是每次操作完了以后一个单一游戏可能分裂成两个单一游戏。实际上这个的处理也非常方便,因为一个状态的后继变成了两个状态,而下一名玩家可以选择两个状态中的任意一个进行操作,所以影响到后继结果的应该是这两个状态构成的一个整体。那么只需要把这两个分别求出来SG值然后“加起来”也就是异或一下然后计入后继就可以了。

1、

POJ2311 Cutting Game

题意:

给出n×m的方格纸片,一次只能剪一刀,最先得到1×1纸片的人获胜。

题解:

对于博弈问题,求SG是比较关键的了
这里的状态是一个二维的,那么就考虑寻找(i,j)的直接后继状态,枚举横着剪还是竖着剪然后递归下去就好啦。

可以发现到2*2的格子是必败状态,sg=0,所以枚举k计算后继的时候从2开始

还有一个比较关键的问题就是对于一个纸片(i,j),把它剪成的两个纸片并不是独立的,也就是状态(i,j)的胜败取决于这两个纸片合起来的SG值而不是其中某一个,所以后继状态的SG值应该是这两个纸片SG值的异或值。这一段话也就是Multi-SG的解法

代码:

#include <cstdio>#include <cstring>#include <algorithm>using namespace std;int sg[205][205],w,h;void get_sg(){    bool ext[40010];    for (int x=2;x<=200;x++)      for (int y=2;y<=200;y++)      {        memset(ext,0,sizeof(ext));        for (int i=2;i+i<=x;i++)//再枚举下去就重复啦          ext[sg[i][y]^sg[x-i][y]]=1;        for (int j=2;j+j<=y;j++)          ext[sg[x][j]^sg[x][y-j]]=1;        for (int k=0;;k++)          if (!ext[k]){sg[x][y]=k; break;}      }}int main(){    get_sg();    while (~scanf("%d%d",&w,&h))      if (sg[w][h]==0) printf("LOSE\n");      else printf("WIN\n");}

2、

[BZOJ2940] [Poi2000] 条纹

题解:

可以类似刚才的题目考虑一下,注意每种决策要分开讨论,因为一种布料不可以放,其他两种可能还能放,Multi-SG的问题分开异或指的是当一块布料把这个棋盘分成两部分时两部分要分开异或

代码:

#include <cstdio>#include <cstring>using namespace std;int sg[1005],c,z,n,m,p;void get_sg(){    bool ext[1005];    for (int i=1;i<=1000;i++)     {        memset(ext,0,sizeof(ext));        for (int j=1;j<=i-c+1;j++) ext[sg[j-1]^sg[i-c-j+1]]=1;        for (int j=1;j<=i-z+1;j++) ext[sg[j-1]^sg[i-z-j+1]]=1;        for (int j=1;j<=i-n+1;j++) ext[sg[j-1]^sg[i-n-j+1]]=1;        for (int j=0;;j++)          if (!ext[j]) {sg[i]=j;break;}     }}int main(){    scanf("%d%d%d",&c,&z,&n);    get_sg();    scanf("%d",&m);    while (m--)    {        scanf("%d",&p);        if (sg[p]) printf("1\n");else printf("2\n");    }}

3、

[POJ3537] Crosses and Crosses

题解:

我们发现当你下在i的时候,对手只要下在i-2,i-1,i+1,i+2都是输了,那只能在剩下的范围内下了
长度为x的棋盘下在i,将游戏分成了两部分(i-3)&(x-i-2)
那么这就是一个典型的Multi-SG游戏了,可以用SG函数来解决。

代码:

#include <cstdio>#include <cstring>using namespace std;int n,sg[2005];void get_sg(){    bool ext[2005];    for (int i=1;i<=2000;i++)     {        memset(ext,0,sizeof(ext));        for (int j=1;j<=i;j++)         {            int a=j-3,b=i-j-2;            if (a<0) a=0; if (b<0) b=0;            ext[sg[a]^sg[b]]=1;        }        for (int j=0;;j++)          if (!ext[j]){sg[i]=j;break;}    }}int main(){    get_sg();    while (~scanf("%d",&n))      if (sg[n]) printf("1\n");else printf("2\n");}

4、

[BZOJ3576] [Hnoi2014] 江南乐

题解:

我们先随便写一个O(a[i]^2)的程序,加上记忆化,呀这不是妥妥TLE吗。
这个k=x/i有点眼熟,在一段数字之内都是一样的,我们要利用这个条件,对了!分块优化!

方便讨论,我们把这个数设为x,除数为m,商为u,余数是r,我们只讨论每次长度都大于等于2的情况,因为小于2时,【x/m】和【x/m+1】都是一次,我们直接算就行了。当长度大于等于2时,r和m-r的奇偶就比较关键,因为只有是奇数才^,偶数次异或都是0,我们可以通过讨论u来解决

当u为奇数时,每次r的奇偶都会变化,但是因为m也每次++,所以m-r的奇偶是不变的,如果ta是偶,对答案没有什么贡献,是奇才有贡献,因为我们是把一次次的操作集中来做,所以每一个都要单独异或。什么意思呢?如果m-r一直是奇,那ta会经历r变成奇数又变成偶数,所以既要添加SGmr又要添加SGmr异或SGr
当u为偶数时,r的奇偶显然不变了,分析同上

代码:

TLE

#include <cstdio>#include <cstring>using namespace std;int sg[100005],a[105],f;int get_sg(int x){    if (sg[x]!=-1) return sg[x];    if (x<f) {sg[x]=0; return 0;}    bool ext[100005];memset(ext,0,sizeof(ext));    for (int i=2;i<=x;i++)    {        int k=x/i,w=0;        int a=x%i,b=i-x%i;        if (a&1) w^=get_sg(k+1);//只有是奇数才^,因为偶数次异或都是0         if (b&1) w^=get_sg(k);        ext[w]=1;    }    for (int i=0;;i++)      if (!ext[i]) {sg[x]=i; break;}    return sg[x];}int main(){    int T,n;    memset(sg,-1,sizeof(sg));    scanf("%d%d",&T,&f);    while (T--)    {        int k=0;        scanf("%d",&n);        for (int i=1;i<=n;i++) scanf("%d",&a[i]),k^=get_sg(a[i]);        if (k) printf("1 ");else printf("0 ");    }}

AC

#include <cstdio>#include <cstring>#include <iostream>using namespace std;int sg[100005],a[105],f;int get_sg(int x){    if (sg[x]!=-1) return sg[x];    if (x<f) {sg[x]=0; return 0;}    int tail;bool ext[100005];memset(ext,0,sizeof(ext));    for (int i=2;i<=x;i=tail+1)    {        tail=min(x,x/(x/i));int w=0,r=x%i,u=x/i;        if (tail-i+1>=2)        {            if (u&1)             {                if ((i-r)&1) w^=get_sg(u);                ext[w]=1;                w^=get_sg(u+1);                ext[w]=1;            }            else            {                if (r&1) w^=get_sg(u+1);                ext[w]=1;                w^=get_sg(u);                ext[w]=1;            }        }        else         {            if (r&1) w^=get_sg(u+1);            if ((i-r)&1) w^=get_sg(u);            ext[w]=1;        }    }    for (int i=0;;i++)      if (!ext[i]) {sg[x]=i; break;}    return sg[x];}int main(){    int T,n;    memset(sg,-1,sizeof(sg));    scanf("%d%d",&T,&f);    for (int ii=1;ii<=T;ii++)    {        int k=0;        scanf("%d",&n);        for (int i=1;i<=n;i++) scanf("%d",&a[i]),k^=get_sg(a[i]);        if (k) printf("1");else printf("0");        if (ii==T) printf("\n");else printf(" ");    }}
原创粉丝点击