zoukankan      html  css  js  c++  java
  • 博弈论

    bzoj2463 谁能赢呢?

    题目大意:给定一个n×n的方格,从(1,1)开始走,每次可以到上下左右没有到过的一个格子,alice先手,交替操作,如果先手必胜则输出'Alice’,否则输出‘Bob’。

    思路:lcomyn大爷秒暴结论。后来仔细想了想,只发现走一步肯定会改变格子奇偶性。其实如果n是偶数,那么就可以用2×1的骨牌覆盖,每次走到另一端后,另一个人走到新格子,所以先手必胜;如果n是奇数,那么就可以去掉第一个格子后骨牌覆盖,胜负正好反过来。

    #include<iostream>
    #include<cstdio>
    using namespace std;
    int main()
    {
        int n;
        while(scanf("%d",&n)==1)
        {
            if (n==0) break;
            if (n%2) printf("Bob
    ");
            else printf("Alice
    ");
        }
    }
    View Code

    bzoj2281 黑白棋(!!!)

    题目大意:一行1*n棋盘,交替放共k个白黑棋子,两个人交替操作,先手黑,后手白,可以选1~d个棋子移动,问先手必胜的初态有几种。

    思路:假设白棋子左移和黑棋子右移没有意义,所以可以把一个白黑棋子看作一对,中间的棋子数是石子数。变成了每次可以从k/2堆石子中选1~d堆任取石子(每堆取得可以不同),先手必败态是:对二进制的每一位,这一位是1的石子堆数%(d+1)都是0(nimk游戏!!!,有一堆n个石子,每次可以取1~k任意个,先手必败态是:n%(k+1)=0,因为先手取x,后手可以取k+1-x)。设fi[i][j]表示前i-1个二进制位,选了j个石子,fi[i+1][j+q*(d+1)*(1<<i)]+=fi[i][j]*c(k/2,q*(d+1))。

    答案是总-先手必败=c(n,k)-fi[up][i]*c(n-i-k/2,k/2)(对于有i个石子的方案,从n-i个位置中选出k对两两相临)

    因为题目中没有之前的假设,所以可能会导致从必败态->必败态,这种转化就是错误的。

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<algorithm>
    #define N 10005
    #define M 105
    #define up 15
    #define p 1000000007LL
    #define LL long long
    using namespace std;
    LL fi[up+1][N],fac[N],inv[N];
    LL mi(LL x,int y){
        LL a=1LL;
        for (;y;y>>=1){
            if (y&1) a=a*x%p;
            x=x*x%p;
        }return a;}
    LL getc(int n,int m){
        if (n<m) return 0LL;
        return fac[n]*inv[m]%p*inv[n-m]%p;}
    void add(LL &a,LL b){a=((a+b)%p+p)%p;}
    int main(){
        int n,k,d,i,j,q,a;LL ans;scanf("%d%d%d",&n,&k,&d);
        for (fac[0]=1LL,i=1;i<=n;++i) fac[i]=fac[i-1]*(LL)i%p;
        for (inv[n]=mi(fac[n],(int)p-2),i=n-1;i>=0;--i) inv[i]=inv[i+1]*(LL)(i+1)%p;
        for (fi[0][0]=1LL,i=0;i<up;++i)
          for (j=0;j<=n-k;++j){
              if (!fi[i][j]) continue;
              for (q=0;j+(a=q*(d+1))*(1<<i)<=n-k&&a<=(k>>1);++q)
                  add(fi[i+1][j+a*(1<<i)],fi[i][j]*getc(k>>1,a)%p);
          }
        for (ans=getc(n,k),i=0;i<=n-k;++i)
          add(ans,-(fi[up][i]*getc(n-i-(k>>1),k>>1)%p));
        printf("%I64d
    ",ans);
    }
    View Code

    sg函数

    一个状态的sg值是它所有后继状态sg值得mex(最小未出现的自然数),sg=0是必败状态。大多情况下,多个子问题的sg的nim和!=0就是总问题可以必胜。

    bzoj1228 E&D

    题目大意:给定n堆石子,两堆一组,每次可以从一组中扔掉一堆,将另一堆分为两堆(每堆个数>=1),问先手有无必胜策略。

    思路:sg函数在很多情况下可以通过打表找规律,然后求nim和判断。这道题目中的sg函数很有规律,斜着看是一个个的三角形,并且有递归的感觉,所以可以从2^30往下递归一下,相应的减去2^x就可以了,最后如果xy都是1,就是0。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define LL long long
    using namespace std;
    int sg(int x,int y){
        int i=1<<30,j=30,ans=31;
        for (;j;--j,i>>=1){
            if (x<=i&&y<=i) ans=j;
            else{
                if (x>i) x-=i;
                if (y>i) y-=i;
            }
        }if (x==1&&y==1) return 0;
        return ans;}
    int main(){
        int t,i,j,n,u,v,cnt;
        scanf("%d",&t);
        while(t--){
            scanf("%d",&n);n/=2;cnt=0;
            for (i=1;i<=n;++i){
                scanf("%d%d",&u,&v);
                cnt^=sg(u,v);
            }if (cnt) printf("YES
    ");
            else printf("NO
    ");
        }
    }
    View Code

    bzoj3576 江南乐

    题目大意:给定n堆石子,两个人轮流操作,操作是:挑一堆不小于f个的,分成不小于2的任意堆,且要求尽量均分。不能操作的人输。

    思路:考虑求sg[i],可以循环j从2~i表示分的堆数,k=i/j,所以i/j+1的有k1=i%j个,i/j的有k2=j-i%j个,这些sg值的异或就是答案(因为两个相同数的异或是0,所以只需要i%j^1=1才会^sg[i/j+1],sg[i/j]同理)。但因为i/j的取值只有根i个,且只需要考虑k1、k2的奇偶就可以了,所以可以做到n根n的复杂度。写成记忆化搜索会快很多。

    #include<iostream> 
    #include<cstdio> 
    #include<cstring> 
    #include<algorithm> 
    #include<ctime> 
    #define N 100001 
    using namespace std; 
    int sg[N]={0},vi[N]={0},f; 
    void pre(){ 
        int i,j,k,k1,k2,la; 
        for (i=f;i<N;++i){ 
            for (j=2;j<=i;j=la+1){ 
                k=i/j;la=i/k; 
                k2=i%j;k1=j-k2; 
                vi[sg[k*(k1&1)]^sg[(k+1)*(k2&1)]]=i; 
                if (j<min(i,la)){ 
                    ++j;k2=i%j;k1=j-k2; 
                    vi[sg[k*(k1&1)]^sg[(k+1)*(k2&1)]]=i; 
                } 
            }for (j=0;vi[j]==i;++j); 
            sg[i]=j; 
        } 
    } 
    int main(){ 
        int t,i,j,n,ci; 
        scanf("%d%d",&t,&f);pre(); 
        while(t--){ 
            scanf("%d",&n);ci=0; 
            for (i=1;i<=n;++i){ 
                scanf("%d",&j);ci^=sg[j]; 
            }if (ci) printf("%d",1); 
            else printf("%d",0); 
            if (!t) printf("
    "); 
            else printf(" "); 
        } 
    }
    View Code

    二分图博弈

    bzoj2437 兔兔与蛋蛋

    题目大意:给定一张棋盘,有白子黑子和空格子,兔兔先手,选一个白格子移到空格子;蛋蛋后手,选一个黑格子移到空格子。给出一个操作序列,问兔兔那几个操作是失误的(认为是失误的当且仅当兔兔操作前有必胜策略,兔兔操作后也有必胜策略)。

    思路:黑白染色,相当于空格子走一些黑白交替的无环的路径,不能操作的人输。把空格子和黑格子看作一样,对那些本身颜色和黑白染色一样的格子编号(只有这些格子可能移动)。如果一个点一定在二分图最大匹配中,认为这个点有必胜策略。所以这道题目中要判断每次兔兔操作前后是否有必胜策略,如果都有,就认为它失误了。动态的二分图匹配。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define N 45
    #define M 10000
    using namespace std;
    int mp[N][N],mt[M]={0},vi[M]={0},tot,point[M]={0},next[M],en[M],fb[M]={0},mi[M],
        id[N][N]={0},dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
    int in(){
        char ch=getchar();
        while(ch!='.'&&ch!='O'&&ch!='X') ch=getchar();
        if (ch=='.') return -1;
        return ch=='X';}
    int ab(int x){return (x<0 ? -x : x);}
    void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;}
    bool find(int u){
        int i,v;
        for (i=point[u];i;i=next[i]){
            if (vi[v=en[i]]==tot||fb[v]) continue;
            vi[v]=tot;
            if (!mt[v]||find(mt[v])){
                mt[v]=u;mt[u]=v;return true;
            }
        }return false;
    }
    int main(){
        int n,m,i,j,k,q,x,y,bx,by,nm=0;scanf("%d%d",&n,&m);
        for (i=1;i<=n;++i)
          for (j=1;j<=m;++j)
              if ((mp[i][j]=in())<0) mp[bx=i][by=j]=1;
        for (i=1;i<=n;++i)
          for (j=1;j<=m;++j)
            if ((mp[i][j]&&(ab(i-bx)+ab(j-by))%2==0)||(!mp[i][j]&&(ab(i-bx)+ab(j-by))%2==1))
              id[i][j]=++nm;
        for (tot=0,i=1;i<=n;++i)
          for (j=1;j<=m;++j)
            if (id[i][j])
              for (k=0;k<4;++k)
                if (id[x=i+dx[k]][y=j+dy[k]])
                  add(id[i][j],id[x][y]);
        for (tot=0,i=1;i<=nm;++i)
            if (!mt[i]){++tot;find(i);}
        scanf("%d",&q);q<<=1;
        for (i=1;i<=q;++i){
            if (mt[id[bx][by]]){
                j=mt[id[bx][by]];
                mt[j]=mt[id[bx][by]]=0;
                fb[id[bx][by]]=1;
                ++tot;if(find(j)) mi[i]=1;
            }else{fb[id[bx][by]]=1;mi[i]=1;}
            scanf("%d%d",&bx,&by);
        }for (nm=0,i=1;i<=q;i+=2)
            if (!mi[i]&&!mi[i+1]) ++nm;
        printf("%d
    ",nm);
        for (i=1;i<=q;i+=2)
          if (!mi[i]&&!mi[i+1]) printf("%d
    ",(i+1)/2);
    }
    View Code

    bzoj1443 游戏

    题目大意:给定一个网格,有些格子不能走,有一枚棋子,每次走到相邻的格子。问选那些起始位置能使后手赢。

    思路:黑白染色,如果一个点不一定在最大匹配上,就是后手赢。所以在匹配每个点是不是一定在就可以了。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define N 105
    #define M 40005
    using namespace std;
    int id[N][N],tot=0,point[M]={0},next[M],en[M],mt[2][M]={0},vi[M]={0},fb[M]={0},mp[N][N],tt=0,
        dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};
    int in(){
        char ch=getchar();
        while(ch!='.'&&ch!='#') ch=getchar();
        return ch=='.';}
    void add(int u,int v){next[++tot]=point[u];point[u]=tot;en[tot]=v;}
    bool find(int k,int u){
        int i,v;
        for (i=point[u];i;i=next[i]){
            if (vi[v=en[i]]==tt||fb[v]) continue;
            vi[v]=tt;
            if (!mt[k][v]||find(k,mt[k][v])){
                mt[k][v]=u;mt[k][u]=v;return true;
            }
        }return false;}
    int main(){
        int n,m,i,j,k,x,y,cnt=0,nm=0;scanf("%d%d",&n,&m);
        for (i=1;i<=n;++i)
          for (j=1;j<=m;++j) if ((mp[i][j]=in())>0) id[i][j]=++nm;
        for (tot=0,i=1;i<=n;++i)
          for (j=1;j<=m;++j)
            if (id[i][j])
              for (k=0;k<4;++k)
                if (id[x=i+dx[k]][y=j+dy[k]])
                  add(id[i][j],id[x][y]);
        for (i=1;i<=nm;++i)
          if (!mt[0][i]){++tt;find(0,i);}
        for (i=1;i<=n;++i)
          for (j=1;j<=m;++j){
              if (!id[i][j]) continue;
              if (!mt[0][id[i][j]]){
                  if (!cnt) printf("WIN
    ");
                  printf("%d %d
    ",i,j);
                  ++cnt;continue;
              }else{
                  for (k=1;k<=nm;++k) mt[1][k]=mt[0][k];
                  fb[id[i][j]]=1;k=mt[1][id[i][j]];
                  ++tt;mt[1][k]=mt[1][id[i][j]]=0;
                  if (find(1,k)){
                    if (!cnt) printf("WIN
    ");
                      printf("%d %d
    ",i,j);++cnt;
                  }fb[id[i][j]]=0;
              }
          }if (!cnt) printf("LOSE
    ");
    }
    View Code

    还有一种写法比较快,用网络流求出最大匹配之后,如果从S通过有流量的边到的左边的点和从T通过没流量的边到的右边的点都是不一定出现的点,就是答案。

    bzoj4600 硬币游戏(!!!

    题目大意:给出一行n个硬币和mq,每次可以选一个反面向上的硬币c*2^i*3^j,(1)选定p、q,p>=1,q>=1&&p*q<=i&&q<=mq,可以把c*2^(i-k*q)*3^j翻过来;(2)选定p、q,p>=1,q>=1&&p*q<=j&&q<=mq,可以把c*2^i*3^(j-k*q)翻过来。

    思路:可以看出和c没关系,可以对c的不同看作不同的游戏,sg异或起来就可以了。除去c之后,可以看作i*j的矩阵,每次可以翻i行或者j列的等差数列的棋子,可以把n个棋子看作x个只有右下角(i,j)是1的独立游戏的sg异或,因为每个棋子如果被翻偶数次是不影响先后手胜负的,对于每一个独立游戏,除了右下角的棋子翻了奇数次,其他都翻了偶数次,所以看作独立的是对的。枚举i、j、p、q看作是i、j的一种后继状态,后继又可以看作一个游戏,sg异或起来,求出mex就是ij的sg了。

    (orz现场a掉的vampire爷)

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define N 15
    #define M 21
    #define up 30001
    using namespace std;
    int in(){
        char ch=getchar();int x=0;
        while(ch<'0'||ch>'9') ch=getchar();
        while(ch>='0'&&ch<='9'){
            x=x*10+ch-'0';ch=getchar();
        }return x;}
    int sg[M][N][N],m2[N],m3[N],ai[up][2],vi[500]={0},vt=0;
    void pre(int mq){
        int i,j,k,p,q,ci,mx;
        for (i=0;i<N;++i)
            for (j=0;m3[j]*m2[i]<up;++j){
                mx=max(i,j);++vt;
                for (q=1;q<=mq;++q)
                    for (p=1;p*q<=mx;++p){
                        if (p*q<=i){
                            ci=-1;
                            for (k=1;k<=q;++k){
                                if (ci==-1) ci=sg[mq][i-p*k][j];
                                else ci^=sg[mq][i-p*k][j];
                            }if (ci!=-1) vi[ci]=vt;
                        }if (p*q<=j){
                            ci=-1;
                            for (k=1;k<=q;++k){
                                if (ci==-1) ci=sg[mq][i][j-p*k];
                                else ci^=sg[mq][i][j-p*k];
                            }if (ci!=-1) vi[ci]=vt;
                        }
                    }
                for (k=0;vi[k]==vt;++k);
                sg[mq][i][j]=k;
            }
    }
    int main(){
        freopen("coin.in","r",stdin);
        freopen("coin.out","w",stdout);
        
        int n,m,i,j,t,ci;t=in();
        for (m2[0]=m3[0]=i=1;i<N;++i){
            m2[i]=m2[i-1]<<1;
            m3[i]=m3[i-1]*3;
        }for (i=1;i<up;++i){
            ai[i][0]=ai[i][1]=0;
            for (j=i;j%2==0;j>>=1) ++ai[i][0];
            for (j=i;j%3==0;j/=3) ++ai[i][1];
        }for (i=1;i<M;++i) pre(i);
        while(t--){
            n=in();m=in();ci=0;
            for (i=1;i<=n;++i)
                if (!in()) ci^=sg[m][ai[i][0]][ai[i][1]];
            if (ci) printf("win
    ");
            else printf("lose
    ");
        }
    }
    View Code
  • 相关阅读:
    学习:大文件统计与排序
    共享库SidebySide之Windows Shared Assembly
    Bundle是个好东西
    所谓的代码段、数据段
    [design decision] common case vs. rare case
    如何给C++设计一个GC
    玩一把tesseract
    [design decision]让工具依赖于naming convention是个拙劣的做法
    监控域名可用性并自动发信
    调试lua代码
  • 原文地址:https://www.cnblogs.com/Rivendell/p/4765414.html
Copyright © 2011-2022 走看看