博弈专题

这次省赛我们队就悲剧在一道非常脑残的博弈题上了,主要责任就在我大哭,想搞好博弈任务艰巨呀

    http://poj.org/problem?id=3710

论文原题:

   1,去环操作, 对于长度为奇数的环sg的值为1,否则为0;

   2,然后就是树的删边游戏:叶子节点的sg值为0,中间节点的sg值为其 后继节点的sg+1的异或和

参考的   http://www.cnblogs.com/staginner/archive/2012/03/09/2387839.html  对于环的处理,用了low[],dfn[] 时间复查度 O(100*500)

http://poj.org/problem?id=1704

   博弈感觉好难想呀!感谢  http://www.cnblogs.com/AndreMouche/archive/2011/03/27/1996762.html   提供证明

把n个棋子分成两组,例如 [ai,ai+1]  ,  [ a[i+2] , a[i+3] ] ,ai+1  和 ai+2 之间的距离是不影响结果的,a[i+2] 向左移动k步,那么ai+3也向左移动k步,那么[ a[i+2] , a[i+3] ] 是不变的。

举个例子吧:

 2 5 7   由于 (2-0-1)^(7-5-1)==0 必败

如果first 移动 2 -> 1  那么 second 移动 1 -> 0

 如果first 移动 5 -> 3  那么 second 移动 7 ->5

这样总能保证每次轮流之后 sg==0 必败

#include <cstdlib>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

int chess[1010];

int cmp(int a,int b)
{
    return a>b;    
}
int main(int argc, char *argv[])
{
    int ca,n,a;
    scanf("%d",&ca);
    while(ca--)
    {
        scanf("%d",&n);
        for(int i=0;i<n;i++)
             scanf("%d",&chess[i]);        
        int ans=0; 
        sort(chess,chess+n,cmp);    
        chess[n]=0;
        for(int i=0;i<n;i+=2)
          ans^=(chess[i]-chess[i+1]-1);
       puts(ans==0?"Bob will win":"Georgia will win");
    }
   // system("PAUSE");
    return EXIT_SUCCESS;
}

A Funny Stone Game    http://blog.sina.com.cn/s/blog_51cea4040100gzdx.html  

感谢以上的博客给我提供思路,写的很详细,貌似我理解的还是不是很透测,对于每堆石子,如果先手每次都取的是标号为奇数数的石子,那么先手肯定是是最后取完石子的;

每次只取1个,每堆在奇数-> 偶数->奇数之间 变化

以上博客的代码:

#include <cstdlib>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;
const int maxn=25;

int sg[maxn],stone[maxn];
int Case,n;
bool vis[maxn];

int Get_sg(int k)
{
   memset(vis,0,sizeof(vis));    
   for(int i=0;i<k;i++)
   for(int j=i;j<k;j++)
   {
      int p=(sg[i]^sg[j]);
      vis[p]=1;
   }
  for(int i=0;;i++) if(!vis[i]) return i; 
}
void init()
{
   sg[0]=0;
   for(int i=1;i<23;i++)
     sg[i]=Get_sg(i);     
}
void work(int now)
{
     Case++;
     for(int i=0;i<n;i++)    
      if(stone[i])
        for(int j=i+1;j<n;j++)
        for(int k=j;k<n;k++)
          if ((((now^sg[n-i-1])^sg[n-j-1])^sg[n-k-1])==0)
          {
               printf("Game %d: %d %d %d\n",Case,i,j,k);
               return;
          }
    printf("Game %d: -1 -1 -1\n",Case);
}
int main(int argc, char *argv[])
{
    init();
   // for(int i=0;i<23;i++)
   //  cout<<sg[i]<<endl;
    while(scanf("%d",&n)==1&&n)
    {
        for(int i=0;i<n;i++)  scanf("%d",&stone[i]);
        int now=0;
        for(int i=0;i<n;i++)
          if(stone[i]&1) now=(now^sg[n-i-1]);
        work(now);  
    }
    //system("PAUSE");
    return EXIT_SUCCESS;
}

http://blog.sina.com.cn/s/blog_51cea4040100tj9u.html


跟着上面博客的大神练习博弈,上面的题目都很好!博主已经解释的已经很好,这里我就加上点自己的想法;

  1, 首先要明白阶梯博弈的模型; 阶梯博弈就是在把偶数阶上的石子拿一部分到奇数阶,或者反过来;如果先手小取的胜利,先手必须要使他操作完后所有的奇数阶的游戏和(即是sg的异或和)== 0 ,应为最后一步肯定是第一阶(奇数阶)向第0阶(偶数阶)移动石子;

  2.   在能够判断先手是否必胜之后,在做进一步的探究:先手第一次操作有多少种不同方案必胜?nim=sg1^sg2^sg3.....,(sgi为奇数阶的sg的值)

     1) 如果是奇数阶梯的石子个数,假设为size,如果(size^nim)  <=  size  就 ans++ ;

     2)如果是偶数阶,他的石子只能向他的下一个奇数阶移动石子,设他的下一个奇数阶移动石子的sg值为size1,如果 (nim^size1) > size1 && (nim^size1 - size1) <=  size  则ans++;


http://acm.hdu.edu.cn/showproblem.php?pid=1524

就是NIM游戏的变形 代码就不贴了

注意在就sg值的时候如果用的是记忆化搜索,要把vis[]数组开成是局部变量,应用记忆花用的递归,我开始就是卡在这了,找了变天


http://acm.hdu.edu.cn/showproblem.php?pid=1848


改变了NIM游戏的每次取的个数不是任意个,而必须是fibo中的每个数,记忆化搜索下即可;

#include <cstdlib>
#include <iostream>

using namespace std;
const int maxn=1010;
int fibo[30],sg[maxn];
void get_sg(int tot)
{
     bool vis[maxn]={0};     
     for(int i=2;i<18&&fibo[i]<=tot;i++)
       vis[sg[tot-fibo[i]]]=1;
     for(int i=0;;i++)  
       if(!vis[i]) { sg[tot]=i;break; }
}
int main(int argc, char *argv[])
{
    int p,n,m;
    fibo[1]=fibo[2]=1;
    for(int i=3;;i++)
    {
        fibo[i]=fibo[i-2]+fibo[i-1];        
        if(fibo[i]>1000) break;
    }
    sg[0]=0;
    for(int i=1;i<=1000;i++)
      get_sg(i);
    while(cin>>n>>m>>p&&(n+m+p))
    {
                       
        int ans=sg[n]^sg[m]^sg[p];
        puts(ans?"Fibo":"Nacci");
    }
   // system("PAUSE");
    return EXIT_SUCCESS;
}

http://acm.hdu.edu.cn/showproblem.php?pid=3800


acm_diy的题,开始做的时候没有看到过的人才5呀,果然是神题,思路肯定没有错,但是实现起来巨复杂,通过打标找规律发现x的sg函数值就是x二进制位数,对于< 2^63的数其sg <=63 ,对于每段sg值相同的区间就构建一次矩阵,状态表示为 dp[len][ state ] (state<=63)  表示长度为1--len其异或值为state的个数  还有对于已经是亮的 还有不确定的,所构建的矩阵式不一样的 ,以后再debug把!就敲到这了

#include <cstdlib>
#include <iostream>

using namespace std;
const int mod=100000007;
typedef long long ll;
long long matrix[70][70];
long long ans[70][70];

void f(ll a[70][70],ll b[70][70],ll c[70][70])
{
     for(int i=0;i<=64;i++)     
     for(int j=0;j<=64;j++)
     {
         c[i][j]=0;    
         for(int k=0;k<=64;k++)
           c[i][j]=(c[i][j]+a[i][k]*b[k][j])%mod;
     }
}
void pow(ll a[70][70],ll b[70][70],ll n)
{
     ll c[70][70],cc=0;     
     while(n&&cc<70)
     {
           cc++;
          if(n&1)
          {
               f(a,b,c);     
               memcpy(a,c,sizeof(c)); 
          }        
          n>>=1;
          f(b,b,c);
          memcpy(b,c,sizeof(c));
     }
}
struct Line
{
   ll l,r;       
}line[10010];

int cmp(Line a,Line b)
{
    if(a.l!=b.l) return a.l < b.l;
    return a.r < b.r;    
}
int main(int argc, char *argv[])
{
    long long n,m,cnt[70],ca,l,r;
    scanf("%lld",&ca);
    while(ca--)
    { 
        scanf("%lld %lld",&n,&m);
        memset(ans,0,sizeof(ans));         
        memset(cnt,0,sizeof(cnt)); 
        for(int i=0;i<m;i++)
           scanf("%lld %lld",&line[i].l,&line[i].r);
        sort(line,line+m,cmp);   
        for(int i=0;i<m;i++)
        {
             l=line[i].l,r=line[i].r;
             if(i!=0&&l<=line[i-1].r) l=line[i-1].r+1;
             if(l>r) continue;
             ll tt=0;
             while((1<<(tt+1))-1<l) tt++;
             if(r<=(1<<(tt+1))-1) cnt[tt+1]+=r-l+1;
             else
             {
               cnt[tt+1]+=(1<<(tt+1))-l;
               tt++;
               while(1) 
               {
                 if((1<<(tt+1))-1<r) cnt[tt+1]+=(1<<tt),tt++;
                 else 
                 {
                     cnt[tt+1]+=r-(1<<tt)+1;break;     
                 }
               }
             }
         }
        ans[0][0]=1;
        ll dd;
        for(dd=1;;dd++)
        {   
              memset(matrix,0,sizeof(matrix));
              for(int i=0;i<=63;i++)
              {
                    matrix[i][i^dd]++;  
                    matrix[i][i]++;  
              }  
              ll num;
              if(n>=(1<<dd)-1) num=1<<(dd-1);
              else num=n-(1<<(dd-1))+1;
              if(num>cnt[dd]) pow(ans,matrix,num-cnt[dd]);
              memset(matrix,0,sizeof(matrix));
              for(int i=0;i<=63;i++)
                    matrix[i][i^dd]++;  
             if(cnt[dd]>0) pow(ans,matrix,cnt[dd]);
              if((1<<dd)-1>=n) break;     
        }
        printf("%lld\n",ans[0][0]);
    }
    //system("PAUSE");
    return EXIT_SUCCESS;
}

http://poj.org/problem?id=2311


这题太蛋疼了,原因就是乍看无法再SG函数的用上(当一个人无法做出决策时那么这个人就输了,其sg=0),我开始的思路是如果当前的形状能够划分成两个必败的形状,则必胜,也不知道错哪了,折腾了好几个小时,在网上搜了代码看到用的是sg函数当(2,2) (2,3),(3,3)当做必败态,然后向上递归

贴下我的wa代码,以后再改

#include <cstdlib>
#include <iostream>
#include <cstring>

using namespace std;
int win[210][210];

void get_sg(int h,int w)
{
     for(int i=2;i+2<=h;i++)    
     {
         if(win[i][w]==-1) get_sg(i,w);        
         if(win[h-i][w]==-1) get_sg(h-i,w);
         if(!win[i][w]&&!win[h-i][w])  { win[h][w]=1; return ; } 
     }
     for(int i=2;i+2<=w;i++)    
     {
         int a=h,b=i;
         if(a>b)  swap(a,b);    
         if(win[a][b]==-1) get_sg(a,b);       
         if(win[a][b])  continue;
         a=h,b=w-i;
         if(a>b)  swap(a,b);
         if(win[a][b]==-1) get_sg(a,b);       
         if(win[a][b])  continue;
          win[h][w]=1; return; 
     }
     win[h][w]=0;
}
int main(int argc, char *argv[])
{
    //freopen("data.in","r",stdin);
   // freopen("data.out","w",stdout);
    memset(win,-1,sizeof(win));
    for(int i=1;i<=200;i++)
      win[1][i]=1;
    win[1][1]=0;  
    win[2][2]=win[2][3]=win[3][3]=0;  
    get_sg(2,4);
    for(int i=2;i<=200;i++)
    for(int j=i;j<=200;j++)
     if(win[i][j]==-1)
     {
         get_sg(i,j);
     }
    int a,b;
    while(cin>>a>>b) 
    {
        if(a>b) swap(a,b);                 
        if(win[a][b]) cout<<"WIN"<<endl;
        else cout<<"LOSE"<<endl;
    }
   // system("PAUSE");
    return EXIT_SUCCESS;
}

http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1185

这题很以往组合博弈题不同,每次选择一堆石子,变成了每次最多选择3堆,然后没堆取走任意颗石子,观察二进制的每一位,一次操纵只能把每一位的改变量最多为3,如果每一位的个数都是4的倍数,那么必败;

#include <cstdlib>
#include <iostream>
#include <cstring>

using namespace std;

int main(int argc, char *argv[])
{
    int n,a,cnt[33];
    while(cin>>n)
    {
        memset(cnt,0,sizeof(cnt));         
        for(int i=0;i<n;i++)         
        {
            cin>>a;
            int t=0;
            while(a)       
            {
                if(a&1) cnt[t]++;
                a>>=1;
                t++;             
            } 
        }
       int i; 
       for(i=0;i<31;i++)
          if(cnt[i]%4!=0) break;
       if(i>=31) cout<<"1"<<endl;
       else cout<<"0"<<endl;   
    }
   // system("PAUSE");
    return EXIT_SUCCESS;
}

http://acm.hdu.edu.cn/showproblem.php?pid=1850

水题

#include <cstdlib>
#include <iostream>
#include <cstring>

using namespace std;

int a[110];
int main(int argc, char *argv[])
{
    int n,ans;
    while(cin>>n&&n)
    {
         ans=0;           
         for(int i=0;i<n;i++)               
         {
             cin>>a[i];        
             ans^=a[i];
         }
         int sum=0;
        if(ans!=0) 
         for(int i=0;i<n;i++)
         {
             if((ans^a[i])<=a[i]) sum++;        
         }
         cout<<sum<<endl;
    }
  // system("PAUSE");
    return EXIT_SUCCESS;
}


http://acm.hdu.edu.cn/showproblem.php?pid=2147

 水题,开始我打标竟然超内存了,后来画图找规律找规矩就可以了

#include <cstdlib>
#include <iostream>
#include <cstring>

using namespace std;

int main(int argc, char *argv[])
{
    int n,m;
    while(cin>>n>>m&&(n||m))
    {
         if(get_sg1(n,m)) cout<<"Wonderful!"<<'\n';
         else cout<<"What a pity!"<<'\n';
    }
  //  system("PAUSE");
    return EXIT_SUCCESS;
}







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值