【NYOJ】取石子系列总结(十一题全)

取石子(一)

基础的巴什博奕

巴什博奕的重点是只有一堆

如果n % (m + 1) != 0 则先手赢,如果用普通的数组会TLE。

证明:如果n = m + 1,先手最多拿m个,肯定有剩下的,所以先手必输,所以碰到k(m + 1)的局面的人必输。

那么如果n = k(m + 1) + s,这个k 就是系数,s < m + 1,那么只要先手拿掉s个,这样后手面对的就是k(m + 1)局面,所以先手在

n % (m + 1) != 0时必输。

#include 
using namespace std;
int a[1000001];
int main(){
	int t,n,m;
	cin>>t;
	while(t--){
		cin>>n>>m;
		if(n % (m + 1) != 0){
			cout<<"Win"<

取石子(二)

这题是尼姆博弈巴什博奕的结合

每一堆是巴什博奕,如果巴什博奕赢了就看成是石子数为1的堆,如果输了就看成是石子数为0的堆(看做没有了),下面面对的就是尼姆博弈,如果1的堆数是2的倍数,就相当于尼姆博弈中的奇异局势,面对这种局势必输,如果石子数为1的堆数不是2的倍数,那么是非奇异局势,必赢。

#include 
using namespace std;
int main(){
	int N,a,b;
	int T;
	cin>>T;
	while(T--){
		cin>>N;
		int ans = 0; 
		for(int i = 0; i < N; i++){
			cin>>a>>b;
			ans ^= (a % (b + 1));
		}
		if(ans) cout<<"Win"<

取石子(三)

 真是看了半天,好多帖子都没讲清楚的感觉,男人八题之一,看上去就非常吓人,但是代码真的很简洁,有点像尼姆博弈。

但是不一样,我们可以分析,

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

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

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

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

以此类推

如果有n堆石子,n为奇数,先手必赢,如果n为偶数,且石子数两两相同,先手必输,否则就赢。

我自己想肯定想不出来的感觉……

#include
#include 
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"<

取石子(四)

普通的威佐夫博弈题,公式有俩,如果符合公式就是奇异局势,面对奇异局势必输,然而公式我还没看懂。先记下来

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

bk = ak + k

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

#include 
#include 
#include 
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"<

取石子(五)

普通的斐波那契博弈

https://blog.csdn.net/dgq8211/article/details/7602807证明过程在这里

结论:如果n是斐波那契数先手必败

#include 
using namespace std;
long long a[100];
void fib(){
	a[1] = 1; a[2] = 1;
	for(int i = 3; i < 100; i++){
		a[i] = a[i - 1] + a[i - 2];
	}
}
int main(){
	long long n;
	fib();
	while(cin>>n){
		bool flag = false;
		for(int i = 2; i < 100; i++){
			if(n == a[i]){
				flag = true;
				break;
			}
		}
		if(flag) cout<<"No"<

取石子(六)

这题就是简单的尼姆博弈,但是要用scanf,或者优化后的cin才能过 

#include 
#include 
using namespace std;

//int a[1005];
int main(){
//	ios::sync_with_stdio(false);
	int n,m;
//	cin>>n;
	scanf("%d",&n);
	while(n--){
		int ans = 0;
		int temp;
//		cin>>m;
		scanf("%d",&m);
		for(int i = 0; i < m; i++){
//			cin>>temp;
			scanf("%d",&temp);
			ans ^= temp;
		} 
		if(ans) //cout<<"PIAOYI"<

取石子(七)

可以一个一个的分析,n为石子数,

n = 1 先手赢

n = 2 先手赢

n = 3 先手输

n = 4 先手输

n = 5 先手如果取1个,后手可以取两个,剩下两堆石子数为1的堆,先手输

         先手如果取2个,后手可以取1个,剩下两堆石子数为1的堆,先手输

n = 6 先手如果取1个,后手取对面的1个,剩下两堆石子数为2的堆,先手输(尼姆博弈)

         先手如果取2个,后手取对面的2个,剩下两堆石子数为1的堆,先手输

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

#include 
using namespace std;
int main(){
	int n;
	while(cin>>n){
		if(n == 1 || n == 2){
			cout<<"Hrdv"<

Wythoff Game 

威佐夫博弈这个博客讲的蛮好,然后这道题也是套公式就可以做出来的,注意最后要换行……我就是没换行然后WA了好多发。

#include 
#include 
using namespace std;
int a[100001][2];
int main(){
	int n;
	for(int i = 1; i <= 100000; i++){
		a[i][0] = i * (sqrt(5.0) + 1) / 2;
		a[i][1] = a[i][0] + i;
	}
	while(cin>>n){
		for(int i = 0; i <= n; i++){
			cout<<"("<

取石子(八)

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

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 
#include 
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"< a){
					break;
				}
				if(min == a && max < b){
					cout<

取石子(九)

想不明白为什么不能直接把异或后的结果作为判断依据,如果尼姆博弈是ans == 0先手输,这里改成先手赢不就好了,为什么还要统计石子数大于1的堆数呢?

后来一看测试数据,假如有两堆石子,分别有2个石子,5个石子

先手取完一堆:不论是2还是5,对手可以把剩下的那堆取的只剩一个,那么先手就输了

先手不取完:不论是在2还是在5中取的只剩1个,对手都可以在另一堆中取的只剩一个,先手也输

在这个例子中,即使2 ^ 5 != 0 先手也是必输的,

对于反尼姆博弈,可以这样分析

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

如果石子数都不为1:

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

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

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

2.石子数全为1,堆数为偶数(异或结果为0),先手赢。

 

#include 
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"<

取石子(十)

学到了学到了

SG函数:sg(n) = mex(sg(m))(m是n的后一个状态) , sg函数等于mex运算上一个状态的值

那么肯定有聪明的孩子要问了,什么是mex运算,什么是m是n的后一个状态呢?

别急,咱给你细细道来,

mex运算是:在不属于当前集合的值中的最小正整数,比如说mex(1,3,5) = 0,mex= (0,2,3) = 1

m是n的后一个状态:比如说有a个石子,n操作取走x个石子,剩下了a - x个石子,m就代表,有a - x个石子。

SG定理:游戏和的SG值等于各小游戏的SG值的异或和

也就是说,把各个子游戏的值都异或,最后就可以得到答案,SG值为0就是输了,大于0就赢

情况1:只能取2的幂

sg[0] = 0

x = 1时,取走1 - f{1},剩{0},sg[1] = mex{0} = 1

x = 2时,取走2 - f{1,2},剩{0,1},sg[2] = mex{0,1} = 2

x = 3时,取走3 - f{1,2},剩{1,2},sg[3] = mex{1,2} = 0

以此类推,这种情况下sg函数值是012的循环,即n%3

情况2:没有规律

情况3:sg[n] = n

情况4:没有规律

情况5:sg[n] = 0 , 1, 0 , 1 .....

情况n(n >= 6):sg[m] = m % (n + 1)

下面的第二种情况和第四种情况都是按照sg函数的定义写的代码,应该挺好看懂的,这里就不赘述了。

//游戏和的SG函数等于各个游戏SG函数的异或和 
#include 
#include 
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"<
取石子博弈总结(先手必胜局面)
巴什博弈 n % (m + 1) != 0
威佐夫博弈 (b - a)*(1 + sqrt(5))\2  != a (b >= a)
尼姆博弈 a1 ^ a2 ^ a3 ... != 0
斐波那契博弈 a != fib{b1,b2,b3...}(不是斐波那契数列里的数)
反尼姆博弈 a1^a2^... && n || ! a1 ^ a2 ^ ... && !n

 

 

 

 

 

 

总结完毕,如果有错漏请不吝指出哦~ 

你可能感兴趣的:(小总结儿,NYOJ)