hdu 5724 - chess (sg函数)

<p>
</p><p>#include<cstdio></p>#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<map>
#include<set>
#include<queue>
#include<stack>
#include<iostream>
using namespace std;
typedef long long LL;

int sg[1<<21];
bool h[25];

void init_SG()
{
    memset(sg,0,sizeof sg);
    for(int i=(1<<20)-1;i>=0;i--)
    {
        memset(h,0,sizeof h);
        for(int j=0;j<20;j++)
        {
            if((i&(1<<j))==0) continue;
            for(int k=j+1;k<20;k++)
            {
                if((i&(1<<k))!=0) continue;
                h[sg[i-(1<<j)+(1<<k)]]=1; break;
            }
        }
        for(int j=0;j<=20;j++) if(h[j]==0) { sg[i]=j; break; }
    }
}

int main()
{
    init_SG();
    int T; scanf("%d",&T);
    while(T--)
    {
        int n; scanf("%d",&n);
        int ans=0;
        for(int i=1;i<=n;i++)
        {
            int k,y=0; scanf("%d",&k);
            while(k--)
            {
                int f; scanf("%d",&f); f--;
                y=y|(1<<f);
            }
            ans=ans^sg[y];
        }
        if(ans) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}


因为一行最多只有20个数,也就是说只有(1<<20)种状态,向右移动表示小的数推向了大的数。可以用SG函数预处理出所有情况。然后把每一行的SG函数值异或一下,非零则必胜,否则输。


组合博弈的通解就是sg函数,学习了sg函数之后一直没有咋用过。

学习博弈的可以在nyoj上面做10道取石子题目,作为了对博弈也就有一定理解了。

用的时候注意初始的时候只要初始sg[0]=0;

其他都通过函数求解。

这里贴一个求解sg函数的模板。

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. int sg[N];  
  2. bool hash[N];  
  3. void sg_solve(int *s,int t,int N)   //N求解范围 S[]数组是可以每次取的值,t是s的长度。  
  4. {  
  5.     int i,j;  
  6.     memset(sg,0,sizeof(sg));  
  7.     for(i=1;i<=N;i++)  
  8.     {  
  9.         memset(hash,0,sizeof(hash));  
  10.         for(j=0;j<t;j++)  
  11.             if(i - s[j] >= 0)  
  12.                 hash[sg[i-s[j]]] = 1;  
  13.         for(j=0;j<=N;j++)  
  14.             if(!hash[j])  
  15.                 break;  
  16.         sg[i] = j;  
  17.     }  
  18. }  


用set容器实现的方法,原理一样。oj上容易超时

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. void sg_solve()  
  2. {  
  3.     memset(sg,0,sizeof(sg));  
  4.     for(int i=1;i<N;i++)  
  5.     {  
  6.         set<int> v;  
  7.         for(int j=0;j<t;j++)  
  8.             if(i - s[j] >= 0)  
  9.                 v.insert(sg[i-s[j]]);  
  10.         int g=0;  
  11.         while(v.count(g)!=0)  
  12.             g++;  
  13.         sg[i]=g;  
  14.     }  
  15. }  



通过一道题目说一下。

hdoj 1536 和pku 2960 S-Nim

题意就是给出一个数组s。为每次可以取石子的数目。

然后给你n堆石子每堆si。求解先手能不能赢!标准的sg函数用法题目。

代码:

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. #include<stdio.h>  
  2. #include<string.h>  
  3. #include <string>  
  4. #include <iostream>  
  5. using namespace std;  
  6.   
  7. const int N = 10008;  
  8. int s[108],t;  
  9. int sg[N];  
  10. bool hash[N];  
  11. void sg_solve(int *s,int t,int N)   //N求解范围 S[]数组是可以每次取的值,t是s的长度。  
  12. {  
  13.     int i,j;  
  14.     memset(sg,0,sizeof(sg));  
  15.     for(i=1;i<=N;i++)  
  16.     {  
  17.         memset(hash,0,sizeof(hash));  
  18.         for(j=0;j<t;j++)  
  19.             if(i - s[j] >= 0)  
  20.                 hash[sg[i-s[j]]] = 1;  
  21.         for(j=0;j<=N;j++)  
  22.             if(!hash[j])  
  23.                 break;  
  24.         sg[i] = j;  
  25.     }  
  26. }  
  27.   
  28. int main()  
  29. {  
  30.     int i,j,n,m,h;  
  31.     while(scanf("%d",&t),t)  
  32.     {  
  33.         string ans="";  
  34.         for(i=0;i<t;i++)  
  35.             scanf("%d",&s[i]);  
  36.         sg_solve(s,t,N);  
  37.         scanf("%d",&n);  
  38.         for(i=0;i<n;i++)  
  39.         {  
  40.             scanf("%d",&m);  
  41.             int res = 0;  
  42.             for(j=0;j<m;j++)  
  43.             {  
  44.                 scanf("%d",&h);  
  45.                 res ^= sg[h];  
  46.             }  
  47.             ans+=res?'W':'L';  
  48.         }  
  49.         cout<<ans<<endl;  
  50.     }  
  51.     return 0;  
  52. }  




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值