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

取石子(一)php

基础的巴什博奕ios

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

若是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时必输。spa

#include <iostream>
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"<<endl;
		}else{
			cout<<"Lose"<<endl;
		}
	}
	return 0;
}

取石子(二).net

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

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

#include <iostream>
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"<<endl;
		else cout<<"Lose"<<endl;
	}
	return 0;
}

取石子(三)

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

可是不同,咱们能够分析,

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

假若有两堆石子,若是两堆的数量同样,先手必输(P局势),若是两堆数量不同,N局势,

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

假若有四堆石子,若是石子数量量相同,P,若是不相同,N。

以此类推

若是有n堆石子,n为奇数,先手必赢,若是n为偶数,且石子数两两相同,先手必输,不然就赢。

我本身想确定想不出来的感受……

#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;
}

取石子(四)

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

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;
}

取石子(五)

普通的斐波那契博弈

http://www.javashuo.com/article/p-kibsjrcj-hz.html证实过程在这里

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

#include <iostream>
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"<<endl;
		else cout<<"Yes"<<endl;
	}
	return 0;
}

取石子(六)

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

#include <iostream>
#include <cstdio>
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"<<endl;
			printf("PIAOYI\n");
		else //cout<<"HRDV"<<endl;
			printf("HRDV\n"); 
	}
	return 0;
}

取石子(七)

能够一个一个的分析,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 <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;
}

Wythoff Game 

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

#include <iostream>
#include <cmath>
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<<"("<<a[i][0]<<","<<a[i][1]<<")";
		}
		cout<<endl;
	}
	return 0;
}

取石子(八)

跟上面那个威佐夫博弈讲的同样,假如能够赢,分两种状况

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;
}

取石子(九)

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

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

先手取完一堆:不管是2仍是5,对手能够把剩下的那堆取的只剩一个,那么先手就输了

先手不取完:不管是在2仍是在5中取的只剩1个,对手均可以在另外一堆中取的只剩一个,先手也输

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

对于反尼姆博弈,能够这样分析

若是石子数都为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;
}

取石子(十)

学到了学到了

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 <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 % (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

 

 

 

 

 

 

总结完毕,若是有错漏请不吝指出哦~