博弈论入门

知识点:

(1)下面这个链接是奢华版数学家博弈论模型,虽然有点难懂,但是如果做了相当的博弈题目,还是会领悟不少

http://www.bubuko.com/infodetail-806583.html

(2)接下来是穷人版码农博弈论模型,只有各种结论。

    取石子游戏:

    1.bash博弈:只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个。最后取光者得胜。

    很显然k*(m+1)是必败点,其余是必胜点。

    和上次多校的时候做到的knight、queen、king、castle那题推导的方式类似。首先澄清,必胜点是指先手没有动,但是他存在一种动的方案,使得对方面对必败点。也就是说,如果所有博弈局面组织成一张图,那么必败点的邻接点全部都是必胜点,而必胜点存在一个邻接点是必败点,而行动就是边(败全胜,胜一败,行动边)。那么这个有什么用呢?bash中显然1~m是必胜点,那么m+1是必败点,那么m+2,m+3....2m+1是必胜点,2m+2是必败点,以此类推。简而言之,就是从初始局面倒推(胜败点交替填写)。从某种意义上说,博弈和动规天然一致。

    2.威佐夫博弈:有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。

 

    sg函数:http://www.cnblogs.com/frog112111/p/3199780.html

奇异局势,所有堆的xor和==0.

假定S是非奇异局势,T是奇异局势。
一堆中石子数量>=2,表示充裕堆, =1表示孤单堆。

S0即非奇异局势下,充裕堆为0的状态
S1即非奇异局势下,充裕堆为1的状态
S2即非奇异局势下,充裕堆>=2的状态

T0即奇异局势下,充裕堆为0的状态
T2即奇异局势下,充裕堆>=2的状态

1.奇异局势的定义可知,S能转移到T,能转移到S, T只能转移到S

2.S0必败,T0必胜

3.S1必胜,因为S1只需要转移到S0即可。

4.S2必胜,T2必败。
1)T2只能转移到S1 和 S2
2)若T2转移到S1 则T2败,若T2转移到S2,S2只需要转回到T2即可。所以S2胜,T2败。

所以:
必胜态:T0,S1,S2
必败态:S0,T2

 

(3)题目:

hdu1404

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

一开局就遇到难题。。。。

给出一个数列每个数字不大于9,轮流要么选一个0删除它以及它右边所有,要么减少一个数字最多到0,删除完数列者获胜。

局面分析感觉比较复杂无规律,所以用sg函数,直接把数列整体看作sg函数的编号。

第二个难点在于sg函数求解,暴力打表一定是超时的,所以换方法,从sg[0]=1往大的sg函数拓展。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<functional>
#include<iostream>
#include<cmath>
#include<string>
#include<cctype>
#include<stack>
#include<set>
#include<map>
#include<ctime>
using namespace std;
#define For(i,k,n) for(int i=k;i<=n;i++)
#define ForD(i,k,n) for(int i=n;i>=k;i--)
#define Lson (x<<1)
#define Rson ((x<<1)+1)
#define MEM(a) memset(a,0,sizeof(a));
#define NEG(a) memset(a,-1,sizeof(a));
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define maxn 999999LL
#define ll long long
int sg[maxn+100],h[maxn+100];
int get_length(int n)//得到整数n的位数 
{
    if(n/100000) return 6;
    if(n/10000) return 5;
    if(n/1000)  return 4;
    if(n/100)   return 3;
    if(n/10)  return 2;
    return 1;
}    
int getnum(string str)
{
    int ret=0,sz=str.size();
    For(i,0,sz-1)
    {
        ret*=10;
        ret+=str[i]-'0';
    }
    return ret;
}
void getSG()
{
    MEM(sg);
    sg[0]=1;
    For(n,1,maxn)
    {
        if(sg[n]) continue;
        int len=get_length(n);
        int i;
        for(i=len;i>=1;i--)//每一个位上加上一个数 
        {
             int m=n;
             int base=1;
             for(int j=1;j<i;j++)  base*=10;
             int tmp=(m%(base*10))/base;
             for(int j=tmp;j<9;j++)
             {
                m+=base;
                sg[m]=1;//m为前者必胜点 
             }    
        }  
        if(len!=6)//长度小于6,则可以在后面加0开头的
        {
            int m=n;
            int base=1;
            for(int i=len;i<6;i++)
            {
                m*=10;
                for(int b=0;b<base;b++)
                     sg[m+b]=1;
                base*=10;
            }    
        }      
    }   
}
int main()
{
    getSG();
    char buf[2000];
    while(scanf("%s",buf)==1)
    {
        int n=getnum(string(buf));
        //printf("%d\n",n );
        if(buf[0]=='0') puts("Yes");
        else  printf(sg[n]==0?"No\n":"Yes\n");
    }
    return 0;
}
View Code

 

hdu1907

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

一开始没有看出是nim。。。

给出一个堆颜色,每一个颜色数量若干,每次只能从同色取若干个,取完者败。

显然颜色就是堆,然后就是反nim。

#include<cstdio>
#include<iostream>
using namespace std;
int main()
{
    int n,t;
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        int cnt1=0,cnt2=0,sum=0;
        for(int i=0;i<n;i++)
        {
            int num;
            scanf("%d",&num);
            sum^=num;
            if(num>=2) cnt1++;
            if(num==1) cnt2++;
        }
        if((sum==0&&cnt1==0)||(sum!=0&&cnt1>=1))
            printf("John\n");
        else printf("Brother\n");
    }
    return 0;
}
View Code

 

hdu1517

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

从1开始每次轮流选择乘以1~9,令数字>=n的获胜

就是必胜必败点交替互推的方法

#include<cstdio>
#include<iostream>
using namespace std;
#define ll long long
int main()
{
    ll n;
    while(cin>>n)
    {
        int flg=0;
        while(n>1)
        {
            if(flg==0){
                if(n%9==0) n/=9;
                else n=n/9+1;
                flg=1;
            }
            else
            {
                if(n%2==0) n/=2;
                else n=n/2+1;
                flg=0;
            }
        }
        if(flg) puts("Stan wins.");
        else puts("Ollie wins.");
    }
    return 0;
}
View Code

 

hdu1849

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

一维棋盘,若干棋子,轮流棋子向左移动,无其他限制,一个格子可以多个棋子,无动作者败。

这种不容易编码的显然不能必败必胜交替这样做

每一个棋子都是一个堆,转化为nim

#include<cstdio>
#include<iostream>
using namespace std;
#define ll long long
int main()
{
    ll n;
    while(cin>>n&&n)
    {
            int sum=0;
        for(int i=0;i<n;i++)
        {
            int num;
            scanf("%d",&num);
            sum^=num;
        }
        if(sum==0)
        {
            printf("Grass Win!\n");
        }
        else printf("Rabbit Win!\n");

    }
    return 0;
}
View Code

 

hdu2188

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

两人轮流捐款每次捐款不超过m元,先使得总额超过n的获胜

就算不知道bash博弈也可以推导出k*(m+1)是必败态

#include<stdio.h>
int main()
{
    int n,m;
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        if(n%(m+1)==0) printf("Rabbit\n");
        else  printf("Grass\n");
    }    
    return 0;
}
View Code

 

hdu3970

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

一个石圈,每次选取一段长m的连续的石头染色,最后没有行动的败

和bash博弈的区别在于,石圈每次染色后都会产生两个子问题,于是就是sg函数了

/* ***********************************************
Author        :kuangbin
Created Time  :2013-11-17 19:20:19
File Name     :E:\2013ACM\比赛练习\2013-11-17\H.cpp
************************************************ */

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <time.h>
using namespace std;
const int MAXN = 1010;
int sg[MAXN];
bool vis[MAXN];
int m;
int mex(int n)
{
    if(sg[n] != -1)return sg[n];
    if(n < m)return sg[n] = 0;
    memset(vis,false,sizeof(vis));
    for(int i = m;i <= n;i++)
        vis[mex(i-m)^mex(n-i)] = true;
    for(int i = 0;;i++)
        if(vis[i] == false)
        {
            sg[n] = i;
            break;
        }
    return sg[n];
}

int main()
{
    int T;
    int n;
    scanf("%d",&T);
    int iCase = 0;
    while(T--)
    {
        scanf("%d%d",&n,&m);
        iCase++;
        if(n < m)
        {
            printf("Case #%d: abcdxyzk\n",iCase);
            continue;
        }
        n -= m;
        memset(sg,-1,sizeof(sg));
        for(int i = 0;i <= n;i++)
            sg[i] = mex(i);
        if(sg[n] == 0)printf("Case #%d: aekdycoin\n",iCase);
        else printf("Case #%d: abcdxyzk\n",iCase);
    }
    return 0;
}
View Code

 

hdu4478

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

感动,写了无数遍终于写对了。。。。。

有n种颜色,m个背包,每个背包指定的各种颜色石头数目,一轮取一个背包,取完之后总是把所有相同颜色的S个石头合成,如果一轮能至少合成一次,则下一轮仍是你的先手。收益为合成次数,使得收益差最大。

显然是dp,dp[j],表示已经用掉背包集合为j时先手能取得的最大收益差,这里坑了很久的地方是坚决不要dp的时候对于轮数进行一重循环,因为集合已经成拓扑序了,如果这里写一重循环会无限tle扑街。转移是如果不能合成,就收益取相反数,否则收益加上当前转移合成的次数。

#include<cstdio>
#include<cstring>
#include<vector>
#include<cstdlib>
#include<algorithm>
#include<functional>
#include<iostream>
#include<cmath>
#include<string>
#include<cctype>
#include<stack>
#include<set>
#include<map>
#include<ctime>
using namespace std;
#define For(i,k,n) for(int i=k;i<=n;i++)
#define ForD(i,k,n) for(int i=n;i>=k;i--)
#define Lson (x<<1)
#define Rson ((x<<1)+1)
#define MEM(a) memset(a,0,sizeof(a));
#define NEG(a) memset(a,-1,sizeof(a));
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define maxn ((1<<21)+100)
#define ll long long
#define print(b,a) cout<<b<<"="<<a<<endl;
#define printbin(b,a){int tmp=a;string s;do{s+=tmp%2+'0';tmp/=2;}while(tmp);reverse(s.begin(),s.end());cout<<"bin "<<b<<"="<<s<<endl;}
#define printarr(i,a,f,b) {For(i,f,b) printf("%d ",a[i]); printf("\n");}
int d[maxn];
int a[maxn][10],G,S,B;
int bag[22][20];
void init(){
    For(i,0,(1<<B)-1)
    {
        For(j,0,B-1) 
        {
            if(((i>>j)&1)==0) continue;
            For(k,1,G)
                a[i][k]+=bag[j][k];
        }
        For(j,1,G) a[i][j]%=S; 
        /*print("i",i);
        printarr(q,a[i],1,G);*/
    }
}
int solve()
{
    d[(1<<B)-1]=0;
        ForD(j,0,(1<<B)-1)
        {
            For(k,0,B-1) 
            {
                if(((j>>k)&1)==0)
                {
                    int s=j|(1<<k);
                    if(d[s]==-INF) continue;
                    int tmp=d[s];
                    int cnt=0;
                    int b[10];
                    For(q,1,G) b[q]=a[j][q]+bag[k][q];
                    For(q,1,G) cnt+=b[q]/S;
                    if(cnt==0)
                         d[j]=max(d[j],-tmp);
                     else 
                         d[j]=max(d[j],tmp+cnt);
                }
            }
        }
    return d[0];
}
int main()
{
    //freopen("in.txt","r",stdin);
    while(scanf("%d%d%d",&G,&B,&S)&&!(G==0&&B==0&&S==0))
    {
            For(j,0,maxn-5) d[j]=-INF;
        MEM(a);
        MEM(bag);
        For(i,0,B-1)
        {
            int n;
            scanf("%d",&n);
            For(j,0,n-1)
            {
                int num;scanf("%d",&num);
                bag[i][num]++;
            }
        }
        init();
        printf("%d\n",solve());
    }
}
View Code

 

hdu4764

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

轮流写数字,一开始数字1~k,每一轮数字要求和之前的差>=1&&<=k,写下不小于n的人输

显然本质就是巴佐夫博弈,值得注意的只是最开始的先手局面是0不是1

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <time.h>
using namespace std;

int n,k;
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    while(scanf("%d%d",&n,&k) == 2)
    {
        if(n == 0 && k == 0)break;
        if((n-1)%(k+1) == 0)printf("Jiang\n");
        else printf("Tang\n");
    }
    return 0;
}
View Code

 

hdu1847

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

给定n张牌,每次只能取add2的幂张,先取完的胜利

开一个win数组纪录是否是必胜态,然后逆序交替填写必胜必败就可以了

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<algorithm>
#include<functional>
#include<iostream>
#include<cmath>
#include<string>
#include<cctype>
#include<stack>
#include<set>
#include<map>
#include<ctime>
using namespace std;
#define For(i,k,n) for(int i=k;i<=n;i++)
#define ForD(i,k,n) for(int i=n;i>=k;i--)
#define Lson (x<<1)
#define Rson ((x<<1)+1)
#define MEM(a) memset(a,0,sizeof(a));
#define NEG(a) memset(a,-1,sizeof(a));
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define maxn ((1<<21)+100)
#define ll long long
#define print(b,a) cout<<b<<"="<<a<<endl;
#define printbin(b,a){int tmp=a;string s;do{s+=tmp%2+'0';tmp/=2;}while(tmp);reverse(s.begin(),s.end());cout<<"bin "<<b<<"="<<s<<endl;}
#define printarr(i,a,f,b) {For(i,f,b) printf("%d ",a[i]); printf("\n");}
int a[10]={1,2,4,8,16,32,64,128,256,512};
int n,win[2000],vis[2000];
int solve(int n){
    MEM(vis);
    win[n]=0;
    ForD(i,0,n-1)
    {
        int loseflg=0;
        For(k,0,9)
        {
            if(i+a[k]<=n&&win[i+a[k]]==0) loseflg=1;
        }
        win[i]=loseflg;
    }
    return win[0];
}
int main()
{
    int n;
    while(scanf("%d",&n)==1)
    {
        if(solve(n))
        {
            puts("Kiki");
        }
        else puts("Cici");
    }
    return 0;
}
View Code

 

hdu2147

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

从棋盘右上角开始轮流往左,下或者左下走,无法行动者败

这题不难,但是如果p、n态轮流填写的话内存有限制,所以打个表找规律

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<algorithm>
#include<functional>
#include<iostream>
#include<cmath>
#include<string>
#include<cctype>
#include<stack>
#include<set>
#include<map>
#include<ctime>
using namespace std;
#define For(i,k,n) for(int i=k;i<=n;i++)
#define ForD(i,k,n) for(int i=n;i>=k;i--)
#define Lson (x<<1)
#define Rson ((x<<1)+1)
#define MEM(a) memset(a,0,sizeof(a));
#define NEG(a) memset(a,-1,sizeof(a));
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define maxn ((1<<21)+100)
#define ll long long
#define print(b,a) cout<<b<<"="<<a<<endl;
#define printbin(b,a){int tmp=a;string s;do{s+=tmp%2+'0';tmp/=2;}while(tmp);reverse(s.begin(),s.end());cout<<"bin "<<b<<"="<<s<<endl;}
#define printarr(i,a,f,b) {For(i,f,b) printf("%d ",a[i]); printf("\n");}
//int t[2001][2001];
int d[3][2]={{1,-1},{0,-1},{1,0}};
int n,m;
/*int dfs(int i,int j)
{
    if(t[i][j]!=-1) return t[i][j];
    t[i][j]=0;
    For(k,0,2)
    {
        int newi=i+d[k][0];
        int newj=j+d[k][1];
        if(newi>=1&&newi<=n&&newj>=1&&newj<=m)
        {
            t[i][j]|=dfs(newi,newj)^1;
        }
    }
    return t[i][j];
}*/
void solve()
{
    if(n%2==0||(n%2==1&&m%2==0)) 
        puts("Wonderful!");
    else puts("What a pity!");
}
int main()
{
    while(scanf("%d%d",&n,&m)==2&&!(n==0&&m==0))
    {
        //NEG(t);
        solve();
    }
}
View Code

 

hdu1850

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

尼姆博弈,问有多少种方案必胜

分析的时候犯傻,尼姆博弈每次只能在一堆取!所以只需考虑每一堆数目是不是够使得总异或为0

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<algorithm>
#include<functional>
#include<iostream>
#include<cmath>
#include<string>
#include<cctype>
#include<stack>
#include<set>
#include<map>
#include<ctime>
using namespace std;
#define For(i,k,n) for(int i=k;i<=n;i++)
#define ForD(i,k,n) for(int i=n;i>=k;i--)
#define Lson (x<<1)
#define Rson ((x<<1)+1)
#define MEM(a) memset(a,0,sizeof(a));
#define NEG(a) memset(a,-1,sizeof(a));
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define maxn 200
#define ll long long
#define print(b,a) cout<<b<<"="<<a<<endl;
#define printbin(b,a){int tmp=a;string s;do{s+=tmp%2+'0';tmp/=2;}while(tmp);reverse(s.begin(),s.end());cout<<"bin "<<b<<"="<<s<<endl;}
#define printarr(i,a,f,b) {For(i,f,b) printf("%d ",a[i]); printf("\n");}
int a[maxn];
int main(){
    int m;
    while(scanf("%d",&m)==1&&m)
    {
        int sum=0,ans=0;
        For(i,1,m){int num;scanf("%d",&a[i]);sum^=a[i];}
        if(sum==0) puts("0");
        else 
        {
            For(i,1,m) if(a[i]>(sum^a[i])) ans++;
            printf("%d\n",ans );
        }
    }
    return 0;
}
View Code

 

hdu1851

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

n堆的bash博弈

每堆的胜负的总异或

#include<stdio.h>
int main()
{
    int T;
    int i,n;
    int ans,m,l;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        ans=0;
        for(i=1;i<=n;i++)
        {
            scanf("%d%d",&m,&l);
            ans=ans^(m%(l+1));
        }    
        if(ans==0)  printf("Yes\n");
        else   printf("No\n");
    }    
    return 0;
}
View Code

 

hdu2897

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

有上下界限的一堆bash博弈

结论很显然,n%(p+q)>=1&&<=p是必败点,值得注意的就两点,1是坑爹的最后换行忘记了居然测试的时候看不出来而且报错wa,2是建议从0开始而不是从n开始做n、p讨论

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<algorithm>
#include<functional>
#include<iostream>
#include<cmath>
#include<string>
#include<cctype>
#include<stack>
#include<set>
#include<map>
#include<ctime>
using namespace std;
#define For(i,k,n) for(int i=k;i<=n;i++)
#define ForD(i,k,n) for(int i=n;i>=k;i--)
#define Lson (x<<1)
#define Rson ((x<<1)+1)
#define MEM(a) memset(a,0,sizeof(a));
#define NEG(a) memset(a,-1,sizeof(a));
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define maxn 200
#define ll long long
#define print(b,a) cout<<b<<"="<<a<<endl;
#define printbin(b,a){int tmp=a;string s;do{s+=tmp%2+'0';tmp/=2;}while(tmp);reverse(s.begin(),s.end());cout<<"bin "<<b<<"="<<s<<endl;}
#define printarr(i,a,f,b) {For(i,f,b) printf("%d ",a[i]); printf("\n");}
int main(){
    int n,p,q;
    while(scanf("%d%d%d",&n,&p,&q)==3)
    {
        int res=n%(p+q);
        if(res>=1&&res<=p)
            printf("LOST\n");
        else printf("WIN\n");
    }
    return 0;
}
View Code

 

hdu

 

posted @ 2016-07-27 19:48  aidgn  阅读(335)  评论(0编辑  收藏  举报