博弈论

(1)巴什博弈

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

这个游戏是有规律的,并且每个人都采取理智最优的话,游戏一开始就决定了胜负。

与之类似的是报数,从1开始报,最少报数加一个,最多报数加3个,谁先报到20谁赢。

第一个人无论是报1,2,3,第二个人每次取4的倍数都是可以的。如此往复一定是第二个人赢。

即存在公式:N=(K+1)*X+M

在本例中K=3,N=20,那么X=5,M=0,如此一来一定是第二个人赢,而如果M!=0,则第一个人报M,第二个人无论报多少,第一个人再重新报M+(K+1),如此第一个人是必胜的。

例子:

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

代码:

 1 #include<iostream>
 2 using namespace std;
 3 
 4 int main()
 5 {
 6     int N,K;
 7     while(scanf("%d%d",&N,&K))
 8     {
 9         if(N==0)break;
10         else
11         {
12             if((N-1)%(K+1)==0)
13                 printf("Jiang\n");
14             else
15                 printf("Tang\n");
16         }
17     }
18     return 0;
19 }

 

(2)威佐夫博弈

有两堆石子,数量任意,可以不同。游戏开始由两个人轮流取石子。游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。最后把石子全部取完者为胜者。现在给出初始的两堆石子的数目,如果轮到你先取,假设双方都采取最好的策略,问最后你是胜者还是败者。

简单判断威佐夫博弈胜负:

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

需要输出第一步取石子策略:

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

关于这个博弈是存在必败格局的,而且必败格局有一定的规律。两堆石子等效,故而假设第一堆数目少与等于第二堆。

第一个必败格局为:(0,0),即如果假设A当前面对此数目石子,说明B已经在上一步骤取完。

第二个必败格局为:(1,2),如果A当前面对此数目石子,A取完为(1,1)或(0,2),B取完之后A面对的格局依旧是(0,0)

第三个必败格局为:(3,5),依次列举为:(4,7)(6,10)。。。

对于威佐夫博弈有如下规律:

我们用a[i]表示失败态中的第一个,b[i]表示失败态中的第二个.(i从0开始).

那么我们可以看到b[i] = a[i]+i;(i >= 0),a[i]是前面的失败态中没有出现过的最小的整数。

1.每个数仅包含在一个失败态中

可以根据递推式得到此性质

2.每个失败态可以转到非失败态

3.每个非失败态都可以转到一个失败态

    k=b-a;if (a-ak)=(b-bk); (a-ak)>0,(b-bk)>0

则转化为(a-(a-ak),b-(b-bk));

     在ak中可以找到与a相等的,或bk中可以找到与a相等的,则转化为(ak,bk);

这个性质决定了,如果当前不是失败态,我们如何让对手输掉这场博弈。

4.可以用下面的方法来判断失败态

a[i] = [i*(1+√5)/2](这里的中括号表示向下取整),b[i]=a[i]+i;

那么这就是一个失败态。

 

利用性质4解决第一个问题:

#include<iostream>
#include<cmath>
using namespace std;

void change(int &a,int &b)
{
    int t=a;
    a=b;
    b=t;
}
int main()
{
    int a,b;
    while(scanf("%d%d",&a,&b)!=EOF)
    {
        if(a>b)
            change(a,b);
        double x=(sqrt(5.0)+1)/2;
        if((int)((b-a)*x)==a)
            printf("0\n");
        else
            printf("1\n");
    }
    return 0;
}

利用性质3和性质4解决第二个问题:

 首先打必败局表,然后写一个二分查找用来定位,再然后就是利用性质3的两条,分别将所有可能的先手步骤打印出来。

思路用的是讨论区最初的那个思路

http://acm.hdu.edu.cn/discuss/problem/post/reply.php?postid=12441&messageid=1&deep=0

虽然可以AC,但是许多人也提到了这个题目的数据比较水,容易通过。

#include<iostream>
#include<cmath>
using namespace std;
#define MAX 1000001
int a[MAX],b[MAX];
int record[MAX+MAX]={0};
int num;
void calc()
{
    int cnt=0;
    record[0]=1;
    int i;
    for(i=1;a[i-1]<=MAX;i++)
    {
        while(record[cnt]!=0)
            cnt++;
        a[i]=cnt;
        b[i]=cnt+i;
        record[a[i]]=1;record[b[i]]=1;
    }
    num=i;
}
int findA(int num,int l,int r)
{
    int mid=(l+r)/2;
    if(a[mid]==num)
        return mid;
    else
    {
        if(l>=r)return 0;
        if(a[mid]>num)
            findA(num,l,mid-1);
        else
            findA(num,mid+1,r);
    }
}
int findB(int num,int l,int r)
{
    int mid=(l+r)/2;
    if(b[mid]==num)
        return mid;
    else
    {
        if(l>=r)return 0;
        if(b[mid]>num)
            findB(num,l,mid-1);
        else
            findB(num,mid+1,r);
    }
}
int main()
{
    
    calc();

    int one,two;
    while(scanf("%d%d",&one,&two))
    {
        if(one==0&&two==0)break;
        double x=(sqrt(5.0)+1)/2;
        if((int)((two-one)*x)==one)
            printf("0\n");
        else
        {
            printf("1\n");
            int k=two-one;
            if((one-a[k]==two-b[k])&&(one-a[k])>0&&(two-b[k])>0)
                printf("%d %d\n",one-(one-a[k]),two-(two-b[k]));
            int onepro=findA(one,0,num-1);
            if(onepro!=0)
                printf("%d %d\n",one,b[onepro]);
            else
            {
                int oneB=findB(one,0,num-1);
                printf("%d %d\n",a[oneB],one);
            }

            
        }
    }

    return 0;
}

输入:5 8

输出:

1

4 7

3 5

posted on 2015-01-30 21:44  holyprince  阅读(249)  评论(0编辑  收藏  举报

导航