博弈论

思想很奇妙,代码很简短的过程

1、巴什博弈

只有一堆n个石子,两个人轮流从这堆石子中子,规定每次至少取一个,最多取m个.最后取光者得胜.
若n%(m+1)=0,则先手必败,否则先手必胜。若n=(m+1)×r+s 则先手先拿走那个s,然后从必败转回必胜
例题:http://acm.hdu.edu.cn/showproblem.php?pid=1846
PN分析法
http://acm.hdu.edu.cn/showproblem.php?pid=2147
博弈论_第1张图片
这个规律怎么找呢?从行和列方向都是奇偶奇偶 必胜必败交错,行和列都是基数那就是必败,都是偶数那就是必胜,单独分析一下基数乘偶数是必胜,可以想到乘积为偶数为必胜

2、斐波那契博弈

https://www.cnblogs.com/henry-1202/p/9744678.html
参考这篇博客,讲的特别好
例题:
http://acm.hdu.edu.cn/showproblem.php?pid=2516

3、威左夫博弈

奇异局势 https://blog.csdn.net/qq_41311604/article/details/79980882
例题:http://acm.hdu.edu.cn/showproblem.php?pid=1527
betty定理:用来推“奇异局势”的一个公式

4、尼姆博弈

有3堆各若干个物品,两个人轮流从某一堆取任意多的物品,规定每次至少取1个,多者不限,最后取光者得胜。
首先自己想一下,就会发现只要最后剩两堆物品一样多(不为零),第三堆为零,那面对这种局势的一方就必败
那我们用(a,b,c)表示某种局势,首先(0,0,0)显然是必败态,无论谁面对(0,0,0) ,都必然失败;第二种必败态是(0,n,n),自己在某一堆拿走k(k ≤ n)个物品,不论k为多少,对方只要在另一堆拿走k个物品,最后自己都将面临(0,0,0)的局势,必败。仔细分析一下,(1,2,3)也是必败态,无论自己如何拿,接下来对手都可以把局势变为(0,n,n)的情形
那这种奇异局势有什么特点呢?

亦或 相同为0,不同为1
任何奇异局势都有a 亦或b 亦或c为0

把不是奇异局势转化成奇异局势 ,(1,2,4)不是奇异局势,那么就把4变成1^2 这样1亦或2 然后再 亦或(1亦或2) 答案一定是0,因为亦或 ,相同为0,不同为1,所以4如何变成1亦或2呢 当然是4-(4-1亦或2)了,或者,亦或是一个很神奇的东西,把4变成3可以由4亦或(k) ,k为(1亦或2亦或4的结果),这个可以推出来,要把4变成(1亦或2) 可以减,也可以直接添上一个亦或4就OK
想详细知道为什么用亦或表示的参考: https://www.cnblogs.com/jiangjun/archive/2012/11/01/2749937.html

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

5、阶梯尼姆(staircase nim)

阶梯博弈:
阶梯的序号如图所示,地面表示第0号阶梯。每次都可以将一个阶梯上的石子向其左侧移动任意个石子,没有可以移动的空间时(及所有石子都位于地面时)输。
策略如下:https://blog.csdn.net/qq_30241305/article/details/51956518

一道阶梯尼姆变形的题目:http://poj.org/problem?id=1704
就是把他捆绑,代码也挺简单的
https://blog.csdn.net/h_666666/article/details/86683679这个是一开始我的代码,是分奇偶写的,其实不用哎,可以直接这么减

#include 
#include 
#include 
#define  maxn 1010
 
using namespace std;
 
int main()
{
    int t,a[maxn];
    scanf("%d",&t);
    while(t--)
    {
        int n,sum=0;
        scanf("%d",&n);
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&a[i]);
        }
        a[0]=0;
        sort(a+1,a+n+1);//要先排序,注意地址不要弄错。
        for(int i=n; i>0; i=i-2)
        {
            sum^=(a[i]-a[i-1]-1);
        }
        if(sum)printf("Georgia will win\n");
        else printf("Bob will win\n");
    }
    return 0;
}

(一个例题:)比较难判断
https://ac.nowcoder.com/acm/contest/897/E
题解:
阶梯博弈变型
如果有奇数堆石子,就在最前面添加一个空堆使其变成偶数堆。所以只需讨论偶数堆的情况。然后再将每相邻的两堆合并成一组。对于合并后的每一组,每一组的sg值为
(x2−x1)%(k+1)。最后把每一组的sg值异或起来,不为0则Bob获胜,否则Alice获胜。
假设必败者移除的是奇数项的堆,那么必胜者只需移除该项后面一项同样数目即可。假设必败者移除的是偶数项的堆,那么问题就转变成了多堆石子博弈问题。

#include 
#include
#include
#include
#include
 
using namespace std;
int a[10010];
 
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n,k;
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
        }
            int ans=0,num=0;
            a[0]=0;
            //if(k!=2)
            //   a[0]=-1;
            for(int i=n;i>0;i-=2)
            {
                num=(a[i]-a[i-1])%(k+1);
                ans^=num;
            }
            if(ans!=0)
                printf("Bob\n");
            else
                printf("Alice\n");
    }
    return 0;
}

5、SG函数 其实这个才是做博弈题的王道呀
SG值:一个点的Sg值就是一个不等于他的后继点的Sg的且大于等于0的最小整数,大概意思就是:在步骤允许的情况下,与前一个必败点的差(也就是说这个差是规定的,能走的,其中一个步数)
参考: https://www.cnblogs.com/ECJTUACM-873284962/p/6921829.html
游戏和的SG函数等于各个游戏SG函数的Nim和。这样就可以将每一个子游戏分而治之,从而简化了问题。而Bouton定理就是Sprague-Grundy定理在Nim游戏中的直接应用,因为单堆的Nim游戏 SG函数满足 SG(x) = x。
SG函数:
首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
对于任意状态 x , 定义 SG(x) = mex(S),其中 S 是 x 后继状态的SG函数值的集合。如 x 有三个后继状态分别为 SG(a),SG(b),SG©,那么SG(x) = mex{SG(a),SG(b),SG©}。 这样 集合S 的终态必然是空集,所以SG函数的终态为 SG(x) = 0,当且仅当 x 为必败点P时。
【实例】取石子问题

有1堆n个的石子,每次只能取{ 1, 3, 4 }个石子,先取完石子者胜利,那么各个数的SG值为多少?

SG[0]=0,f[]={1,3,4},

x=1 时,可以取走 f{1}个石子,剩余{0}个,所以 SG[1] = mex{ SG[0] }= mex{0} = 1;

x=2 时,可以取走 f{1}个石子,剩余{1}个,所以 SG[2] = mex{ SG[1] }= mex{1} = 0;

x=3 时,可以取走 f{1,3}个石子,剩余{2,0}个,所以 SG[3] = mex{SG[2],SG[0]} = mex{0,0} =1;

x=4 时,可以取走4- f{1,3,4}个石子,剩余{3,1,0}个,所以 SG[4] = mex{SG[3],SG[1],SG[0]} = mex{1,1,0} = 2;

x=5 时,可以取走5 - f{1,3,4}个石子,剩余{4,2,1}个,所以SG[5] = mex{SG[4],SG[2],SG[1]} =mex{2,0,1} = 3;

以此类推…

x 0 1 2 3 4 5 6 7 8…

SG[x] 0 1 0 1 2 3 2 0 1…

由上述实例我们就可以得到SG函数值求解步骤,那么计算1~n的SG函数值步骤如下:

1、使用 数组f 将 可改变当前状态 的方式记录下来。

2、然后我们使用 另一个数组 将当前状态x 的后继状态标记。

3、最后模拟mex运算,也就是我们在标记值中 搜索 未被标记值 的最小值,将其赋值给SG(x)。

4、我们不断的重复 2 - 3 的步骤,就完成了 计算1~n 的函数值。

例题1:http://acm.hdu.edu.cn/showproblem.php?pid=1847
例题2:http://acm.hdu.edu.cn/showproblem.php?pid=1848

例题二代码

import java.util.Scanner;

class Main{

    public static void main(String[] args) {
        while (true){
            Scanner sc = new Scanner(System.in);
            int m = sc.nextInt();
            int n = sc.nextInt();
            int p = sc.nextInt();
            if(m==0 && n==0 && p==0) {
                break;
            }
            initf();
//            for(int i = 0;i<20;i++){
//                System.out.println(f[i]+ " ");
//            }
            getsg(m,n,p);
            System.out.println(sg[m]+" "+sg[n]+" "+sg[p]);
            if((sg[m] ^sg[n]^sg[p])==0) System.out.println("Nacci");
            else System.out.println("Fibo");

        }


    }
    static int max=1005;
    static int f[] = new int[max+5];
    static void initf(){
        f[0] = 1;
        f[1] = 2;
        f[2] = 3;
        for(int i = 3;i<max;i++){
            f[i] = f[i-1]+f[i-2];
        }
    }

    static int sg[] = new int[max];
    

    static int s[] =new int[max]; //为后既点的集合

    static void getsg(int m,int n,int p){
        sg[0] = 0;
        int i;int j;
        sg = new int[max];
        for( i = 1;i<=n;i++){//每一堆从1到max都打一次表
            s = new int[max];
            for(j  =0;(f[j]<=i&& j<=max);j++){  
                s[sg[i-f[j]]] = 1;//当前后既点进行标记

            }
            for(int x = 0;;x++){
                if(s[x]==0){
                    sg[i] =x;
                    break;
                }
            }

        }
    }
}

你可能感兴趣的:(算法博弈)