【nyoj】博弈论

准备5月ACM

先准备肝博弈论,时间不够就先套版思想,参考大佬的结论:https://blog.csdn.net/I_HOPE_SOAR/article/details/82344116

2:https://blog.csdn.net/Dog_dream/article/details/80445886?tdsourcetag=s_pctim_aiomsg

3:https://blog.csdn.net/ojshilu/article/details/16812173

取石子一

最简单的巴什博弈:

适用范围:

1.只有一堆n个

2.每次只能取M个

整体思想:(第一次与每次迭代)能否赢就看n是否能被(m+1)整除,能整除必输

反之就能赢

#include<iostream>
using namespace std;
int main()
{
	int n,num1,g;
	cin>>n;
	while(n--)
	{
		cin>>num1>>g;
        //判断该堆能被每次取的最大值整除
		if(num1%(g+1)!=0)
			cout<<"Win"<<endl;
		else
			cout<<"Lose"<<endl;
	}
	return 0;
}

取石子游戏五

适用条件:斐波那契博弈,斐波那契数必败

1.第一次取不能取完,至少取1颗.

2.从第二次开始,每个人取的石子数至少为1,至多为对手刚取的石子数的两倍。

刚看到测试数据:

n(2<=n<2^64)吓我一跳,这得是多大的数组啊,感谢度娘斐波那契:a100=3.54225e+020数组够了

暂定默认数组长度最长为:

const long long MAXN=5e5+5;//静态声明
long long a[MAXN];

int 长度看机子,16位或32位

long long 32或64

float 4BYTE(cout<<sizeof(float);)double 8BYTE

奇怪,按套路上来应该没问题,前三组测试数据都过了,报答案错误

去杭电的那个试试了,一次过了

#include<iostream>
using namespace std;
long long a[105];

int main()
{
	a[1]=1;a[2]=1;
	for(int i=3;i<=100;i++)
		a[i]=a[i-1]+a[i-2];
	 
	long long n;
	while(cin>>n && n!=0)
	{
		bool flag=0;
		 for(int i=1;i<=100;i++)
		{
			if(a[i]==n)
				{
					flag=1;
					break;
				}	
		 } 
		 if(flag==1)
		 	cout<<"Second win"<<endl;
		 else
		  	cout<<"First win"<<endl;
	}
	return 0;
}

未完待续--

2019-4-16

取石子三

适用条件:

每次只能选择N堆石子中的一堆,取一定数量的石子(最少取一个),取过子之后,还可以将该堆石子中剩下的任意多个石子中随意选取几个放到其它的任意一堆或几堆上

推论:偶等必输

假如有一堆石子,先手必赢(N局势),

假如有两堆石子,如果两堆的数量一样,先手必输(P局势),如果两堆数量不一样,N局势,

假如有三堆石子,先手必赢(N局势)

假如有四堆石子,如果石子数量量相同,P,如果不相同,N。

以此类推

如果有n堆石子,n为奇数,先手必赢,如果n为偶数,且应该是:存在!石子数两两相同,

(4,4,6,6,6,6)也必输,因为可以转为(4,4,4,4)先手必输,否则就赢。
--------------------- 
感谢作者的推论:想永远与你同梦 

根据这位作者的推论,认为代码可以更简洁一点,判断偶数堆石子数是否两两相等就好了,

自己试了一下发现实现有点麻烦,而且理解错误。。

大佬果然厉害,判断方法都这么巧妙,使用桶排序,控制进入偶数,如果桶里是奇数则输

#include<iostream>
#include<cstring> 
using namespace std;
int a[105];
int main(){
	int n;
	while(cin>>n && n){
		int flag = 0;
		memset(a,0,sizeof(a));
		for(int i = 0; i < n; i++){
			int t;
			cin>>t;
			a[t]++;
		}
		if(n & 1)//先判断是不是奇数,赢定了 
			cout<<"Win"<<endl;
		else{//控制偶数进入 
			for(int i = 0; i <= 100; i++){
				if(a[i] & 1){//桶中如果不能被2除断 
					flag = 1;
					break;
				}
			}
			if(flag) cout<<"Win"<<endl;
			else cout<<"Lose"<<endl;
		}
	}
	return 0;
}


取石子六尼姆博弈

适用条件:

有n堆石子,两个人轮流从其中某一堆中任意取走一定的石子,最后不能取的为输家,注意: 每次只能从一堆取任意个,可以取完这堆,但不能不取

#include<iostream>
#include<cstdio>
using namespace std;
int main()
{
    int n,m;
    cin>>n;
    while(n--)
    {
        //不要用数组,直接读一个判断一个
        cin>>m;
        int zero=0;
        for(int i=1;i<=m;i++)
        {
            int v;
            scanf("%d",&v);//大循环读入用scanf优化
            zero^=v;
        }
        zero==0?(cout<<"HRDV"<<endl):(cout<<"PIAOYI"<<endl);//这个调调也挺有趣的
    }
    return 0;
}

取石子二(巴什+尼姆)

题目要求:

共有N堆石子,已知每堆中石子的数量,并且规定好每堆石子最多可以取的石子数(最少取1颗)。

两个人轮流取子,每次只能选择N堆石子中的一堆,取一定数量的石子(最少取一个),并且取的石子数量不能多于该堆石子规定好的最多取子数,等哪个人无法取子时就表示此人输掉了游戏。

1:一看这个,尼姆博弈:每次只能选择N堆石子中的一堆,取一定数量的石子(最少取一个)

2:发现有点不对,有每次取的数目要求。。规定好每堆石子最多可以取的石子数。这不是巴什博弈的例子嘛

那就是分别判断两个博弈赢的情况就好了,刚开始想的是数组先存一个然后巴什赢了的就直接尼姆,后来发现没那么简单

看了大佬的题解:每一堆是巴什博奕,如果巴什博奕赢了就看成是石子数为1的堆,如果输了就看成是石子数为0的堆(看做没有了)

豁然开朗,因为博弈论本质上是解释奇异的状态,所以通过巴什改变状态模拟之后就是尼姆博弈来分流。

#include<iostream>
using namespace std;
int main()
{
	int t,sum;
	cin>>t;
	while(t--)
	{
		sum=0;
		int n;
		cin>>n;
		while(n--)
		{
			int z,g; 
			cin>>z>>g;
			sum^=z%(g+1); //直接一边判别一边进行
		}
		sum==0?(cout<<"Lose"<<endl):(cout<<"Win"<<endl);
	}
	return 0;
}

取石子四威佐夫博弈题

一看过率79%,真的是眼泪掉下来,本人做不来真是拉低了平均水平。

题目特征:

1:定死有两堆石子,数量任意,可以不同。

2:游戏规定,每次有两种不同的取法,

(1)一是可以在任意的一堆中取走任意多的石子;

(2)二是可以在两堆中同时取走相同数量的石子。最后把石子全部取完者为胜者。

“愿梦想与你同在”大佬原答:普通的威佐夫博弈题,公式有俩,如果符合公式就是奇异局势,面对奇异局势必输,然而公式我还没看懂。先记下来

ak = (int)k * (1 + sqrt(5)/2)

bk = ak + k

然后代码就是套第一个公式,如果符合就输定了

//这是大佬的代码,库函数的调调还可以

#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
int main(){
	int a,b;
	while(cin>>a>>b){
		int n = min(a,b);
		int m = max(a,b);
		double k = (double)m - n;
		int temp = (int)(k * (1 + sqrt(5))/2);
		if(temp == n){
			cout<<"0"<<endl;
		}else cout<<"1"<<endl;
	}
	return 0;
}

自己码一个

#include<iostream>
#include<cmath>
using namespace std;
int main(){
	int a,b;
	while(cin>>a>>b)
	{
		int n,m;
		a<b?(n=a,m=b):(n=b,m=a);
		double k = (double)m - n;
		int temp = (int)(k * (1 + sqrt(5.0))/2);
		temp==n?(cout<<"0"<<endl):cout<<"1"<<endl;//n是小的那个 
	}
	return 0;
}

取石子九反尼姆博弈

有n堆石子,两个人轮流从其中某一堆中任意取走一定的石子,最后不能取的为赢家,注意: 每次只能从一堆取任意个,可以取完这堆,但不能不取。

看到这题不就和尼姆博弈反过来嘛,改了次序发现不对,有坑

分析就自己看大佬原贴的推理过程

这是结论:

如果石子数都为1:堆数为偶数时,先手必输,为奇数时,先手必胜。

如果石子数都不为1:

堆数为偶数时,先手每一步,不管是在一堆中取的只剩一个,还是都取完,后手都可以做同样的动作,最后先手是必输的。

堆数为奇数时,先手最后总可以留1个给后手,先手赢

所以,1.存在石子数大于1的堆,堆数为奇数(也就是异或结果为1)先手赢

2.石子数全为1,堆数为偶数(异或结果为0),先手赢。
直接贴大佬代码:尼姆博弈逻辑没肝,推不出来

#include <iostream>
using namespace std;
int main(){
	int m,n;
	cin>>m;
	while(m--){
		cin>>n;
		int ans = 0,temp,s = 0;
		for(int i = 0; i < n; i++){
			cin>>temp;
			if(temp > 1) s++;
			ans ^= temp;
		}
		if(ans && s || !ans && !s){
			cout<<"Yougth"<<endl;
		}else cout<<"Hrdv"<<endl;
	}
	return 0;
}

取石子七类约瑟夫圈博弈

这个思路比较简单

不管先手怎么取,后手总能形成数量相等的两堆石子,使先手面对尼姆博弈中的奇异局势,从而后手获胜,所以可怜的先手只能在n <= 2时获胜,后面就憋想了。


#include <iostream>
using namespace std;
int main(){
	int n;
	while(cin>>n){
		if(n == 1 || n == 2){
			cout<<"Hrdv"<<endl;
		}else cout<<"Yougth"<<endl;
	}
	
	return 0;
}

取石子八

==太孤了,刚看以为就是威佐夫博弈

后面发现还要求第一个怎么取:

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

跟上面那个威佐夫博弈讲的一样,假如可以赢,分两种情况

1.从两堆中取数量一样的石子,因为从两堆中取数量一样的,所以差值不会变,还是b - a,由此算出奇异局势的两个值,与a,b大小比较,要都小,就可以输出

2.从一堆中取任意数量石子,差值从1循环到b,由差值算出min,max,如果是交叉相等有四种情况

min = a && max <= b

min = b && max <= a

max = a && min <= b

max = b && min <= a

但是第二种不可能厚,所以就是这种样子啦。


#include <iostream>
#include <cmath>
using namespace std;
 
void swap(int &aa, int &bb){
	int t = aa; aa = bb; bb = t;
}
 
int main(){
	int a,b,big;
	bool flag = false;
	while(cin>>a>>b && a && b){
		if(a > b) swap(a,b);//保证a小b大 
		int c = b - a;//差值 
		int temp = c * (sqrt(5) + 1.0) / 2.0;
		if(temp == a){//奇异局势 
			cout<<"0"<<endl;
		}else{//取一次使其变成奇异局势 
			cout<<"1"<<endl;
			if(c == 0){
				cout<<"0 0"<<endl;
			}else{
				int bb = temp + c;
				if(bb <= b && temp <= a){
					cout<<temp<<" "<<bb<<endl;
				}
			}
			for(int i = 1;i <= b; i++){
				int min = i * (sqrt(5) + 1) / 2;
				int max = min + i;
				if(min > a){
					break;
				}
				if(min == a && max < b){
					cout<<min<<" "<<max<<endl;
				}else if(max == b && min < a){
					cout<<min<<" "<<max<<endl;
				}else if(max == a && min < b){
					cout<<min<<" "<<max<<endl;
				}
			}
				
		}		
		
	}
	return 0;
}

取石子十反套路博弈论

==肝不动了,看看大佬的代码,要是省赛有用就谢天谢地


//游戏和的SG函数等于各个游戏SG函数的异或和 
#include <iostream>
#include <cstring>
using namespace std;
int f[25],ff[1000];
int sg2[1005],vis[1005],sg4[1005];
 
void fib(){
	f[1] = 1; f[2] = 1;
	for(int i = 3; i <= 20; i++){
		f[i] = f[i - 1] + f[i - 2];
	}
	return;
}
 
void for2(){
	ff[1] = 1;
	for(int i = 1; i <= 500; i++){
		ff[i + 1] = i * 2;
	}
}
int fun2(int n){
	fib();
	memset(sg2,0,sizeof(sg2));
	for(int i = 1; i <= n; i++){
		memset(vis,0,sizeof(vis));
		for(int j = 1; f[j] <= i; j++)
			vis[sg2[i - f[j]]] = 1;
		for(int j = 0; j <= n; j++){
			if(vis[j] == 0){
				sg2[i] = j;
				break;
			}
		}
	}
	return sg2[n];
}
 
int fun4(int n){
	for2();
	memset(sg4,0,sizeof(sg4));
	for(int i = 1; i <= n; i++){
		memset(vis,0,sizeof(vis));
		for(int j = 1; ff[j] <= i; j++){
			vis[sg4[i - ff[j]]] = 1;
		}
		for(int j = 0; j <= n; j++){
			if(vis[j] == 0){
				sg4[i] = j;
				break;
			}
		}
	}
	return sg4[n];
}
 
int main(){
	int n,a;
	while(cin>>n && n){
		int ans = 0;
		for(int i = 1; i <= n; i++){
			cin>>a;
			int sg = 0;
			if(i == 1){
				ans ^= (a % 3);
			}else if(i == 2){
				ans ^= fun2(a);
			}else if(i == 3){
				ans ^= a;
			}else if(i == 4){
				ans ^= fun4(a);
			}else if(i == 5){
				ans ^= (a % 2);
			}else{
				ans ^= (a % (i + 1));
			}
		}
		if(ans)
			cout<<"Yougth"<<endl;
		else 
			cout<<"Hrdv"<<endl;
	}
	
	return 0;
}

有n堆石子,每一堆的规则如下:

第一堆每次只能取2的幂次(即:1,2,4,8,16…);

第二堆只能取菲波那契数列中的元素(即每次只能取1,2,3,5,8…等数量,斐波那契数即后面的数是前面两个数的和);

第三堆可以取任意多个,最小1个,最多可以把这一堆石子取完;

第四堆只能取1和偶数个(即:1,2,4,6,8,10...);

第五堆只能取奇数个(即:1,3,5,7,9.....);

好吧,这样下去太烦人了,六堆及其以后每堆最多取的数量为堆数编号,即第六堆只能取(1,2,3,4,5,6),第七堆只能取(1,2,3,4,5,6,7)....

别看规则很多,但Yougth和Hrdv都是聪明人,现在由Yougth先取,比赛规定谁先取完所有石子既为胜者,输出胜者的名字。

//游戏和的SG函数等于各个游戏SG函数的异或和 
#include <iostream>
#include <cstring>
using namespace std;
int f[25],ff[1000];
int sg2[1005],vis[1005],sg4[1005];
void fib(){
    f[1] = 1; f[2] = 1;
    for(int i = 3; i <= 20; i++){
        f[i] = f[i - 1] + f[i - 2];
    }
    return;
}
void for2(){
    ff[1] = 1;
    for(int i = 1; i <= 500; i++){
        ff[i + 1] = i * 2;
    }
}
int fun2(int n){
    fib();
    memset(sg2,0,sizeof(sg2));
    for(int i = 1; i <= n; i++){
        memset(vis,0,sizeof(vis));
        for(int j = 1; f[j] <= i; j++)
            vis[sg2[i - f[j]]] = 1;
        for(int j = 0; j <= n; j++){
            if(vis[j] == 0){
                sg2[i] = j;
                break;
            }
        }
    }
    return sg2[n];
}
int fun4(int n){
    for2();
    memset(sg4,0,sizeof(sg4));
    for(int i = 1; i <= n; i++){
        memset(vis,0,sizeof(vis));
        for(int j = 1; ff[j] <= i; j++){
            vis[sg4[i - ff[j]]] = 1;
        }
        for(int j = 0; j <= n; j++){
            if(vis[j] == 0){
                sg4[i] = j;
                break;
            }
        }
    }
    return sg4[n];
}
int main(){
    int n,a;
    while(cin>>n && n){
        int ans = 0;
        for(int i = 1; i <= n; i++){
            cin>>a;
            int sg = 0;
            if(i == 1){
                ans ^= (a % 3);
            }else if(i == 2){
                ans ^= fun2(a);
            }else if(i == 3){
                ans ^= a;
            }else if(i == 4){
                ans ^= fun4(a);
            }else if(i == 5){
                ans ^= (a % 2);
            }else{
                ans ^= (a % (i + 1));
            }
        }
        if(ans)
            cout<<"Yougth"<<endl;
        else 
            cout<<"Hrdv"<<endl;
    }
    return 0;
}

--------------------- 
来自作者:想永远与你同梦 
来源:CSDN 
原文:https://blog.csdn.net/I_HOPE_SOAR/article/details/82344116 
版权声明:本文为博主原创文章,转载请附上博文链接!

发布了27 篇原创文章 · 获赞 5 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_38304672/article/details/89322321