zoukankan      html  css  js  c++  java
  • 博弈论题目总结(二)——SG组合游戏及变形

    SG函数

    为了更一般化博弈问题,我们引入SG函数

    SG函数有如下性质:

    1.如果某个状态SG函数值为0,则它后继的每个状态SG函数值都不为0

    2.如果某个状态SG函数值不为0,则它至少存在一个后继的状态SG函数值为0

    如果某个局面SG函数值为0,则该局面先手必败

    放到有向图中,该有向图的核就是SG值为0的点构成的集合

    游戏的和

    游戏的和的SG函数值=所有子游戏SG函数值的异或和Xor

    如果所有子游戏都进行完毕,那么Xor=0,必败

    如果某个状态的SG函数值为0,那么后手一定可以做出一种动作,保持Xor=0,那么先手必败。

    反之某个状态的SG函数值不为0,先手可以让Xor=0,变成后手,重复上述动作,那么先手必胜

    这样就能轻松合并多个独立的组合游戏啦

    mex函数

    $sg[$当前局面$]=mex(sg[$后继局面$])$

    $mex$函数表示第一次还没出现的数

    某种后继局面可能是很多个子游戏,那么该后继局面的$sg$函数就是这些子游戏的和,即子游戏$sg$函数的异或和

    SG组合游戏

    NIM游戏

    有n堆石子,两个人玩游戏,每次轮流在一堆里取走任意个,取走最后一堆的最后一个石子的人赢,问谁赢

    一堆石子相当于一个子游戏,显然该子游戏的SG函数值为该堆中石子数

    再根据游戏的和的思想,把子游戏合并就能求出谁赢了

    NIMk游戏

    同样是NIM游戏,现在变成了每次在k堆中取任意个

    NIM游戏采取策略的根本是,保证当SG函数值为0时,不论先手如何操作,后手一定能做出一种动作,保持Xor=0

    把每堆石子都转化成k+1进制数,再进行不进位的加法即可

    SG组合游戏

    POJ 2960 S-Nim (SG函数递推)

    裸题,考察对SG函数的理解。利用游戏的和以及mex函数。暴力递推出石子SG函数即可

     1 #include <cmath>
     2 #include <cstdio>
     3 #include <cstring>
     4 #include <algorithm>
     5 #define N1 10010
     6 #define ll long long 
     7 #define ull unsigned long long 
     8 using namespace std;
     9 
    10 const int inf=0x3f3f3f3f;
    11 int gint()
    12 {
    13     int ret=0,fh=1;char c=getchar();
    14     while(c<'0'||c>'9'){if(c=='-')fh=-1;c=getchar();}
    15     while(c>='0'&&c<='9'){ret=ret*10+c-'0';c=getchar();}
    16     return ret*fh;
    17 }
    18 
    19 int n,m,o,T,A,B,de;
    20 int s[N1],a[N1],sg[N1],use[N1];
    21 
    22 int main()
    23 {
    24     while(scanf("%d",&m)) {
    25         
    26     if(m==0) break;
    27     int i,j,ans=0,flag;
    28     memset(sg,0,sizeof(sg));
    29     for(i=1;i<=m;i++) s[i]=gint();
    30     sg[0]=0;
    31     for(i=1;i<=10000;i++)
    32     {
    33         for(j=1;j<=m;j++) if(i>=s[j]) use[sg[i-s[j]]]=1;
    34         for(j=0;j<=m;j++) if(!use[j]){ sg[i]=j; break; }
    35         for(j=1;j<=m;j++) if(i>=s[j]) use[sg[i-s[j]]]=0;
    36     }
    37     
    38     o=gint(); 
    39     while(o--) {
    40     
    41     n=gint();
    42     for(i=1;i<=n;i++) a[i]=gint();
    43     for(i=1,ans=0;i<=n;i++) ans^=sg[a[i]];
    44     if(ans) putchar('W'); else putchar('L');
    45 
    46     }
    47     puts("");
    48         
    49     }
    50     return 0;
    51 }
    View Code

    BZOJ 2940 条纹 (SG函数递推)

    稍微复杂了一点,但也没什么好说的,暴力递推SG函数就行了

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <algorithm>
     4 #define il inline 
     5 #define N1 1010
     6 using namespace std;
     7 const int maxn=1000;
     8  
     9 template <typename _T> void read(_T &ret)
    10 {
    11     ret=0; _T fh=1; char c=getchar();
    12     while(c<'0'||c>'9'){ if(c=='-') fh=-1; c=getchar(); }
    13     while(c>='0'&&c<='9'){ ret=ret*10+c-'0'; c=getchar(); }
    14     ret=ret*fh;
    15 }
    16  
    17 int T,n,A,B,C;
    18 int use[N1],sg[N1];
    19  
    20 int main()
    21 {
    22     scanf("%d%d%d",&A,&B,&C);
    23     if(A>B) swap(A,B); if(A>C) swap(A,C); if(B>C) swap(B,C);
    24     int i,j,k;
    25     for(i=A;i<=maxn;i++) 
    26     {
    27         for(j=0;j+A<=i;j++) use[sg[j]^sg[i-j-A]]=1;
    28         for(j=0;j+B<=i;j++) use[sg[j]^sg[i-j-B]]=1;
    29         for(j=0;j+C<=i;j++) use[sg[j]^sg[i-j-C]]=1;
    30         for(j=0;j<=maxn*maxn;j++) if(!use[j]){ sg[i]=j; break; }
    31         for(j=0;j+A<=i;j++) use[sg[j]^sg[i-j-A]]=0;
    32         for(j=0;j+B<=i;j++) use[sg[j]^sg[i-j-B]]=0;
    33         for(j=0;j+C<=i;j++) use[sg[j]^sg[i-j-C]]=0;
    34     }
    35     scanf("%d",&T);
    36     while(T--)
    37     {
    38         scanf("%d",&n);
    39         if(sg[n]) puts("1"); else puts("2");
    40         //printf("%d
    ",f[n]); 
    41     }
    42     return 0;
    43 }
    44  
    45 /*
    46      
    47 */
    View Code

    一个不错的模型转化

    POJ 1704 Georgia and Bob (阶梯博弈)

    题目大意:略

    把相邻两个数的距离的差值-1看成一堆石子,第一堆石子数是第一个数到1的距离

    那么把第i个数向左移x格相当于把x个石子从第i堆放到第i+1堆里,而挪第n堆的石子则是把石子移出游戏

    游戏结束状态就是所有的石子都移出了游戏

    发现从右往左数的偶数堆(第2,4,6..堆)的石子是无意义的,因为先手挪,后手就跟着挪,先手只会输

    我们只考虑从右往左数的奇数堆,先手把石子从奇数堆移动到了偶数堆,相当于把这些石子移除游戏,也就是删除了奇数堆中的这些石子!

    问题转化成了NIM游戏!我们只对从右往左数的奇数堆讨论即可

     1 #include <cmath>
     2 #include <cstdio>
     3 #include <cstring>
     4 #include <algorithm>
     5 #define N1 10010
     6 #define ll long long 
     7 #define ull unsigned long long 
     8 using namespace std;
     9 
    10 const int inf=0x3f3f3f3f;
    11 int gint()
    12 {
    13     int ret=0,fh=1;char c=getchar();
    14     while(c<'0'||c>'9'){if(c=='-')fh=-1;c=getchar();}
    15     while(c>='0'&&c<='9'){ret=ret*10+c-'0';c=getchar();}
    16     return ret*fh;
    17 }
    18 
    19 int n,m,T,A,B,de;
    20 int a[N1],sg[N1];
    21 
    22 int main()
    23 {
    24     scanf("%d",&T);
    25     while(T--){
    26         
    27     int i,j,ans=0;
    28     scanf("%d",&n);
    29     for(i=1;i<=n;i++) a[i]=gint();
    30     sort(a+1,a+n+1);
    31     for(i=2;i<=n;i++) sg[n-i+1]=a[i]-a[i-1]-1; sg[n]=a[1]-1;
    32     for(i=1;i<=n;i+=2) ans^=sg[i];
    33     if(!ans) puts("Bob will win");
    34     else puts("Georgia will win");
    35         
    36     }
    37     return 0;
    38 }
    View Code

    很多问题里状态很大,我们不能预处理出SG函数值,只能打表找规律

    博弈问题的精髓是打表!

    HDU 3032 Nim or not Nim (SG函数打表)

    题目大意:NIM游戏,每次操作还可以把一堆石子分成两堆,问谁赢

    先预处理出单独一堆石子时的sg函数值。

    那么,该游戏的sg函数值=每堆石子的sg函数值的异或和

    暴力枚举后继状态,打个表找规律就行了

     1 #include <cmath>
     2 #include <cstdio>
     3 #include <cstring>
     4 #include <algorithm>
     5 #define N1 1000050
     6 #define ll long long 
     7 #define dd double
     8 using namespace std;
     9 const dd eps=1e-7;
    10 
    11 template <typename _T> void read(_T &ret)
    12 {
    13     ret=0; _T fh=1; char c=getchar();
    14     while(c<'0'||c>'9'){ if(c=='-') fh=-1; c=getchar(); }
    15     while(c>='0'&&c<='9'){ ret=ret*10+c-'0'; c=getchar(); }
    16     ret=ret*fh;
    17 }
    18 
    19 int T,n;
    20 int a[N1],sg[N1],use[N1];
    21 const int maxn=10000;
    22 
    23 int main()
    24 {
    25     scanf("%d",&T);
    26     while(T--){
    27     
    28     scanf("%d",&n);
    29     int i,j,sum=0;
    30     for(i=1;i<=n;i++) 
    31     {
    32         read(a[i]);
    33         if(a[i]%4==3) sum^=a[i]+1;
    34         else if(a[i]%4==0) sum^=a[i]-1;
    35         else sum^=a[i];
    36     }
    37     if(!sum) puts("Bob"); else puts("Alice");
    38     
    39     }
    40     return 0;
    41 }
    42 
    43 /*
    44 scanf("%d",&n);
    45     int i,j;
    46     //for(i=1;i<=n;i++) scanf("%d",&a[i]);
    47     sg[0]=0; sg[1]=1; 
    48     for(i=2;i<=n;i++)
    49     {
    50         use[sg[0]]=1;
    51         for(j=1;j<i;j++) use[sg[j]]=1, use[sg[j]^sg[i-j]]=1;
    52         for(j=0;j<=i*2;j++) if(!use[j]){ sg[i]=j; break; }
    53         use[sg[0]]=0;
    54         for(j=1;j<i;j++) use[sg[j]]=0, use[sg[j]^sg[i-j]]=0;
    55     }
    56     //for(i=0;i<=n;i++) printf("%d:%d
    ",i,sg[i]);
    57     for(i=1;i<=n;i++) printf("%d
    ",sg[i]);    
    58 */
    View Code

    HDU 4644 Triangulation (SG函数打表)

    题目大意:圆上有$a_{i}$个点,两个人玩游戏,轮流在这些点之间连边,边和边不能交叉,现在有n个圆,问谁赢

    和上面的题一模一样的套路,每次连线都会产生两个子游戏,先写暴力打表,然后找规律

    规律比较丧病

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <algorithm>
     4 #define il inline 
     5 #define N1 1000010
     6 using namespace std;
     7 
     8 template <typename _T> void read(_T &ret)
     9 {
    10     ret=0; _T fh=1; char c=getchar();
    11     while(c<'0'||c>'9'){ if(c=='-') fh=-1; c=getchar(); }
    12     while(c>='0'&&c<='9'){ ret=ret*10+c-'0'; c=getchar(); }
    13     ret=ret*fh;
    14 }
    15 
    16 int T,n;
    17 int a[N1];
    18 int sg1[]={0,1,1,2,0,3,1,1,0,3,3,2,2,4,0,5,2,2,3,3,0,1,1,3,0,2,1,1,0,4,5,2,7,4,0,1,1,2,0,3,1,1,0,3,3,2,2,4,4,5,5,2};
    19 int sg2[]={3,3,0,1,1,3,0,2,1,1,0,4,5,3,7,4,8,1,1,2,0,3,1,1,0,3,3,2,2,4,4,5,5,9};
    20 
    21 int main()
    22 {
    23     scanf("%d",&T);
    24     while(T--) {
    25 
    26     scanf("%d",&n);
    27     int i,j,sum=0;
    28     for(i=1;i<=n;i++)
    29     {
    30         read(a[i]);
    31         if(a[i]<=52) sum^=sg1[a[i]-1];
    32         else sum^=sg2[(a[i]-53)%34];
    33     }
    34     if(sum) puts("Carol"); else puts("Dave");
    35 
    36     }
    37     return 0;
    38 }
    39 
    40 /*
    41     
    42 */
    View Code

    SG组合游戏变形

    气氛变得怪异起来

    一些SG组合游戏的终止状态比较特殊,我们可以通过转化解决

    POJ 3537 Crosses and Crosses

    题目大意:给出一个1*n的网格,一开始全都是白格子,两个人轮流把一个白格子涂黑,谁先涂出来连续3个黑格子谁就赢了

    游戏的结束状态不容易直接搞啊

    我们剖析游戏本身的性质

    先手把一个格子涂黑后,它左右一共连续5个格子(边界另外讨论)一定不能被后手涂

    相当于每次涂黑一个格子,删掉连续的不超过5个格子,两侧剩下的格子构成了一个或两个子游戏

    依次求出长度为1~n的连续格子的游戏的SG函数即可

     1 #include <queue>
     2 #include <cmath>
     3 #include <vector>
     4 #include <cstdio>
     5 #include <cstring>
     6 #include <algorithm>
     7 #define N1 2010
     8 #define M1 200010
     9 #define ll long long 
    10 #define dd double
    11 using namespace std;
    12 const dd eps=1e-7;
    13 
    14 int n,tl;
    15 int sg[N1],use[N1],que[N1];
    16 
    17 int main()
    18 {
    19     scanf("%d",&n);
    20     if(n==1){ puts("1"); return 0; }
    21     if(n==2){ puts("2"); return 0; }
    22     if(n==3){ puts("1"); return 0; }
    23     if(n==4){ puts("1"); return 0; }
    24     sg[0]=0; sg[1]=sg[2]=sg[3]=1; sg[4]=2;
    25     int i,j;
    26     for(i=5;i<=n;i++)
    27     {
    28         que[++tl]=sg[i-4]; que[++tl]=sg[i-3]; 
    29         for(j=0;j+5<=i;j++) que[++tl]=sg[j]^sg[i-j-5];
    30         for(j=1;j<=tl;j++) use[que[j]]=1;
    31         for(j=0;j<=i;j++) if(!use[j]){ sg[i]=j; break; }
    32         while(tl) use[que[tl--]]=0;
    33     }
    34     if(sg[n]>0) puts("1");
    35     else puts("2");
    36     return 0;
    37 }
    View Code

    POJ 2311 Cutting Game

    题目大意:给出一张n*m的纸,每次可以把它剪成两半,先剪出来1*1小纸片的人赢

    虽然1*1是必败局面,但并不容易直接推出其他格子的SG函数

    显然x>1时,1*x的局面先手必胜。而2*2局面必败,进而可以推出2*3,3*3也都是必败局面

    利用这两点就可以轻松推出整张纸的SG函数了

     1 #include <queue>
     2 #include <cmath>
     3 #include <vector>
     4 #include <cstdio>
     5 #include <cstring>
     6 #include <algorithm>
     7 #define N1 205
     8 #define M1 200010
     9 #define ll long long 
    10 #define dd double
    11 using namespace std;
    12 const dd eps=1e-7;
    13 
    14 int T,n,m,de;
    15 int sg[N1][N1],use[N1*2]; 
    16 
    17 int main()
    18 {
    19     int i,j,k,x,y,ans,flag;
    20     for(i=1;i<=200;i++) sg[1][i]=1;
    21     for(i=1;i<=200;i++) sg[i][1]=1;
    22     for(i=2;i<=200;i++) for(j=2;j<=200;j++) //if(i+j>4)
    23     {
    24         for(k=2;k<j-1;k++) use[sg[i][k]^sg[i][j-k]]=1; //if(i+k>=4&&i+j-k+1>=4) 
    25         for(k=2;k<i-1;k++) use[sg[k][j]^sg[i-k][j]]=1; //if(j+k>=4&&j+i-k+1>=4) 
    26         
    27         for(k=0;k<=200;k++) if(!use[k]){ sg[i][j]=k; break; }
    28         
    29         for(k=2;k<j-1;k++) use[sg[i][k]^sg[i][j-k]]=0; //if(i+k>=4&&i+j-k+1>=4) 
    30         for(k=2;k<i-1;k++) use[sg[k][j]^sg[i-k][j]]=0; //if(j+k>=4&&j+i-k+1>=4) 
    31     }
    32     while(scanf("%d%d",&n,&m)!=EOF)
    33     {
    34         if(sg[n][m]) puts("WIN");
    35         else puts("LOSE");
    36     }
    37     return 0;
    38 }
    View Code

    BZOJ 1457 棋盘游戏

    题目大意:给出一个坐标系,上面有很多个皇后,皇后只能向左/向下/向左下走,两个人轮流每次选择一个皇后移动,谁先把皇后移动到(0,0)谁赢

    如果直接把(0,0)当做游戏终止局面的话,求解的问题就是谁先把所有皇后都移动到(0,0)谁赢了

    显然如果存在x=y||x=0||y=0的皇后,先手必胜

    所以两个人都极力避免自已移动出来上述三种情况的皇后

    而皇后只能向左下移动,最终皇后一定集中在(1,2)和(2,1)

    我们把SG函数为0的位置设为(1,2)和(2,1)即可

     1 #include <queue>
     2 #include <cmath>
     3 #include <vector>
     4 #include <cstdio>
     5 #include <cstring>
     6 #include <algorithm>
     7 #define N1 105
     8 #define M1 200010
     9 #define ll long long 
    10 #define dd double
    11 using namespace std;
    12 const dd eps=1e-7;
    13  
    14 int T,n;
    15 int sg[N1][N1],use[N1]; 
    16  
    17 int main()
    18 {
    19     int i,j,k,x,y,ans,flag;
    20     for(i=1;i<=100;i++) for(j=1;j<=100;j++) if(i!=j)
    21     {
    22         for(k=1;k<j;k++) if(k!=i) use[sg[i][k]]=1;
    23         for(k=1;k<i;k++) if(k!=j) use[sg[k][j]]=1;
    24         for(k=1;k<min(i,j);k++) use[sg[i-k][j-k]]=1;
    25          
    26         for(k=0;k<200;k++) if(!use[k]){ sg[i][j]=k; break; }
    27          
    28         for(k=1;k<j;k++) if(k!=i) use[sg[i][k]]=0;
    29         for(k=1;k<i;k++) if(k!=j) use[sg[k][j]]=0;
    30         for(k=1;k<min(i,j);k++) use[sg[i-k][j-k]]=0;
    31     }
    32     scanf("%d",&T);
    33     while(T--)
    34     {
    35         scanf("%d",&n); ans=0,flag=0;
    36         for(i=1;i<=n;i++) 
    37         {
    38             scanf("%d%d",&x,&y);
    39             if(x==y||!x||!y) flag=1;
    40             ans^=sg[x][y];
    41         }
    42         if(ans||flag) puts("^o^");
    43         else puts("T_T");
    44     }
    45     return 0;
    46 }
    View Code

    还有一些更加奇怪的变形..

    POJ 3480 John (anti-SG组合游戏)

    题目大意:$n$堆石子的$NIM$游戏,改成取走最后一个石子的人输,问谁赢

    先求出该游戏的$sg$函数

    <1>$sg$函数为0,且每堆石子数量都是1,显然先手必胜

    <2>$sg$函数为1,且每堆石子数量都是1,显然先手必败

    那单堆石子数量>1的情况呢?

    <3>$sg$函数为0,且存在一堆石子数量>1,先手必败

    (1)先手取走了一个石子的石子堆,把$sg$函数变成1

    显然后手一定能把$sg$函数还原成0

    最后一定会还剩下至少2个"石子数量>1的堆",且此时$sg$函数值为0

    不论先手怎么取,sg函数都会变得>1

    $NIM$游戏具有对称性!

    即对于一个$sg$函数值为0的局面而言,不论先手如何操作,后手都能把$sg$函数调成0

    而这种情况下,一定存在石子数>1的堆,所以后手肯定能保持$sg$函数值为1!

    最后会剩下一个石子被先手取走,后手赢

    (2)先手取走了石子数量>1的石子堆中的任意数量个,把$sg$函数变成>1

    此时一定还剩下"石子数量>1的堆",后手也能把$sg$函数调成1

    先手如果取单个石子的堆,后手也跟着取,保持$sg$函数值为1即可

    <4>$sg$函数为>1,且存在一堆石子数量>1,先手必胜

    先手把局面调成<3>就能让对手必败了

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <algorithm>
     4 #define N1 5050
     5 using namespace std;
     6 
     7 int n,m,T;
     8 
     9 template <typename _T> void read(_T &ret)
    10 {
    11     ret=0; _T fh=1; char c=getchar();
    12     while(c<'0'||c>'9'){ if(c=='-') fh=-1; c=getchar(); }
    13     while(c>='0'&&c<='9'){ ret=ret*10+c-'0'; c=getchar(); }
    14     ret=ret*fh;
    15 }
    16 
    17 int a[N1];
    18 
    19 int main()
    20 {
    21     scanf("%d",&T);
    22     while(T--)
    23     {
    24         scanf("%d",&n);
    25         int i,sum=0,ma=0;
    26         for(i=1;i<=n;i++) read(a[i]), sum^=a[i], ma=max(ma,a[i]);
    27         if(ma==1){
    28             if(sum) puts("Brother");
    29             else puts("John");
    30         }else{
    31             if(sum) puts("John");
    32             else puts("Brother");
    33         }
    34     }
    35     return 0;
    36 }
    View Code

    HDU 3595 GG and MM (every-SG组合游戏)

    题目大意:两个人玩游戏,每个子游戏有两堆石子,设石子较少那一堆数量为$x$,那么当前操作的人要在较多的那一堆中取走$kx$个,$k$是正整数且$kx$不能超过石子数量。两个人必须轮流对每一个能操作的子游戏进行操作,结束最后一个游戏的人获胜。

    我们希望必胜的游戏一直保持下去,必败的局面早点结束

    先处理出每种子游戏的$sg$函数

    定义$step$函数表示先手令该游戏结束的最优步数,必胜局面取最大步数,必败局面取最小步数,可得

    $step[u]=$

    $0;(sg[u]=0)$

    $min(step[v])+1;(sg[u]=0,sg[v]>0)$

    $max(step[u])+1;(sg[u]>0,sg[v]=0)$

    那么先手必胜当且仅当单一游戏中最大的$step$为奇数

    这里的$sg$函数的用途是分析局面何时结束,$sg$函数本身并不能决定胜负

     1 #include <cstdio>
     2 #include <cstring>
     3 #include <algorithm>
     4 #define il inline 
     5 #define N1 1010
     6 using namespace std;
     7 const int maxn=1000;
     8 const int inf=0x3f3f3f3f;
     9 
    10 template <typename _T> void read(_T &ret)
    11 {
    12     ret=0; _T fh=1; char c=getchar();
    13     while(c<'0'||c>'9'){ if(c=='-') fh=-1; c=getchar(); }
    14     while(c>='0'&&c<='9'){ ret=ret*10+c-'0'; c=getchar(); }
    15     ret=ret*fh;
    16 }
    17 
    18 int T,n,A,B,C,de;
    19 int use[N1*N1],sg[N1][N1],step[N1][N1];
    20 
    21 int main()
    22 {
    23     
    24     int i,j,k,w,ans,a,b;
    25     for(i=1;i<=maxn;i++)
    26     for(j=1;j<=i;j++)
    27     {
    28         if(i==3&&j==2)
    29             de=1;
    30         for(k=j;k<=i;k+=j) use[sg[max(i-k,j)][min(i-k,j)]]=1;
    31         for(k=0;k<=maxn*maxn;k++) if(!use[k]){ sg[i][j]=k; break; }
    32         if(!sg[i][j]) step[i][j]=inf;
    33         for(k=j;k<=i;k+=j) 
    34         {
    35             a=max(i-k,j), b=min(i-k,j);
    36             if(!sg[i][j]&&sg[a][b]) {
    37                 step[i][j]=min(step[i][j],step[a][b]);
    38             }else if(!sg[a][b]){
    39                 step[i][j]=max(step[i][j],step[a][b]);
    40             }
    41             use[sg[a][b]]=0;
    42         }
    43         step[i][j]++;
    44     }
    45     
    46     while(scanf("%d",&n)!=EOF) {
    47     
    48     ans=0;
    49     for(i=1;i<=n;i++) 
    50     {
    51         scanf("%d%d",&A,&B);
    52         if(A<B) swap(A,B);
    53         ans=max(ans,step[A][B]);
    54     }
    55     if(ans&1) puts("MM"); else puts("GG");
    56     
    57     }
    58     return 0;
    59 }
    60 
    61 /*
    62     
    63 */
    View Code

    BZOJ 1393 Knight (every-SG组合游戏+打表)

    打表没商量,思路和上一题差不多

    这道题也启示我们一个技巧,辅助函数(本题中的$step$函数)和$sg$函数之间可能存在某种神♂秘的关系,如果表本身的规律不够明显,尝试分类讨论打表

    例如此题中,$sg$函数值为0的状态step函数很有规律。而$sg$值不为0的状态的$step$函数,可以根据$sg$函数值为0的后继状态推出来

      1 #include <cstdio>
      2 #include <cstring>
      3 #include <algorithm>
      4 #define il inline 
      5 #define N1 200010
      6 using namespace std;
      7 const int maxn=1000;
      8 const int inf=0x3f3f3f3f;
      9  
     10 template <typename _T> void read(_T &ret)
     11 {
     12     ret=0; _T fh=1; char c=getchar();
     13     while(c<'0'||c>'9'){ if(c=='-') fh=-1; c=getchar(); }
     14     while(c>='0'&&c<='9'){ ret=ret*10+c-'0'; c=getchar(); }
     15     ret=ret*fh;
     16 }
     17  
     18 int m,n,de;
     19  
     20 inline int check(int x,int y)
     21 {
     22     if(x==n||y==n)
     23     {
     24         if(x==n) swap(x,y);
     25         if(n%4==0){ if(x==n) return 0; return 1; }
     26         if(n%4==1){ if(x==n-1) return 1; return 0; }
     27         if(n%4==2){ if(1<=x%4&&x%4<=2) return 0; return 1; }
     28         if(n%4==3){ return 1; }
     29     }
     30     if( 1<=x%4 && x%4<=2 && 1<=y%4 && y%4<=2 ) 
     31         return 0;
     32     return 1;
     33 }
     34 inline int query(int x,int y)
     35 {
     36     if(x==n||y==n)
     37     {
     38         if(x==n) swap(x,y);
     39         if(n%4==0)
     40         { 
     41             if(x<=3) return n/2-1;
     42             if(x==n) return n/2-1+((x-1)/4)*2+1;
     43             return n/2-1+(x/4)*2;
     44         }
     45         if(n%4==1)
     46         {
     47             if(x==n) return n-1;
     48             if(x==n-1) return n-2;
     49             return n/2+(x-1)/4*2;
     50         }
     51         if(n%4==2)
     52         {
     53             return n/2-1+(x-1)/2;
     54         }
     55         if(n%4==3)
     56         {
     57             return n/2+x/4*2;
     58         }
     59     }
     60     if(!check(x,y)) return (x/4+y/4)*2;
     61     int ans=0;
     62     if(1<=x-2 && y+1<=n && !check(x-2,y+1)) ans=max(ans,query(x-2,y+1)+1);
     63     if(1<=x-2 && y-1>=1 && !check(x-2,y-1)) ans=max(ans,query(x-2,y-1)+1);
     64     if(1<=x-1 && 1<=y-2 && !check(x-1,y-2)) ans=max(ans,query(x-1,y-2)+1);
     65     if(x+1<=n && 1<=y-2 && !check(x+1,y-2)) ans=max(ans,query(x+1,y-2)+1);
     66     return ans;
     67 }
     68 int xx[N1],yy[N1],step[N1];
     69 void solve()
     70 {
     71     int i,j,x,y,ma,tmp;
     72     for(j=1,ma=0;j<=m;j++)
     73     {
     74         scanf("%d%d",&xx[j],&yy[j]);
     75         ma=max(ma,query(xx[j],yy[j]));
     76     }
     77     if(ma&1){
     78      
     79     puts("YES"); 
     80     for(j=1;j<=m;j++)
     81     {
     82         x=xx[j]; y=yy[j]; tmp=query(x,y);
     83         if(1<=x-2 && y+1<=n) if(tmp==query(x-2,y+1)+1){ printf("%d %d
    ",x-2,y+1); continue; }
     84         if(1<=x-2 && y-1>=1) if(tmp==query(x-2,y-1)+1){ printf("%d %d
    ",x-2,y-1); continue; }
     85         if(1<=x-1 && 1<=y-2) if(tmp==query(x-1,y-2)+1){ printf("%d %d
    ",x-1,y-2); continue; }
     86         if(x+1<=n && 1<=y-2) if(tmp==query(x+1,y-2)+1){ printf("%d %d
    ",x+1,y-2); continue; }
     87     }
     88      
     89     }else puts("NO"); 
     90 }
     91  
     92  
     93 int main()
     94 {
     95     int i,j,k,w,ans,x,y;
     96     scanf("%d%d",&m,&n);
     97     //if(n<=100) S1::solve(); else S2::solve();
     98     solve();
     99     return 0;
    100 }
    View Code
  • 相关阅读:
    使用fscanf读取文本文件
    实验室开发机系统结构图
    字符串是否为数字及有效性检查
    winXP系统通过蓝牙在笔记本和手机之间传递数据
    HarborGIS3D场景编辑器速成街区数据
    判断一个浮点数是否为NAN(INF)
    单文档中只是想得到当前View类的指针
    windows控制台中使用不同颜色显示不同类型的日志
    c++中enum 如何使用
    陈佩斯
  • 原文地址:https://www.cnblogs.com/guapisolo/p/10447575.html
Copyright © 2011-2022 走看看