博弈論題目集 (持續更新)

巴什博弈  HDU 1846

1、  本遊戲是一個二人遊戲;
2、  有一堆石子一共有n個;
3、  兩人輪流進行;
4、  每走一步可以取走1…m個石子;
5、  最先取光石子的一方爲勝;

這個應該比較好推:

如果 n % (m+1)==0 後手勝利 否則 先手勝利

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int n,m;
		scanf("%d%d",&n,&m);
		if(n%(m+1)==0) printf("second\n");
		else printf("first\n");
	}
	return 0;
}

斐波拉契博弈 HDU 2516

有一堆個數爲n(n>=2)的石子,遊戲雙方輪流取石子,規則如下:

1、先手不能在第一次把所有的石子取完,至少取1顆;

2、之後每次可以取的石子數至少爲1,至多爲對手剛取的石子數的2倍;

3、約定取走最後一個石子的人爲贏家;

f[1]=1,f[2]=2的斐波那契數列

如果n爲斐波拉契數列的一項,那麼先手必敗,否則先手必勝。

有篇解釋的很清楚的博客:https://blog.csdn.net/dgq8211/article/details/7602807###

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
ll f[50];
int main(){
	f[1]=1,f[2]=2;
	for(int i = 3; i <= 48; i++) f[i]=f[i-1]+f[i-2];
	//printf("%lld\n",f[48]);
	ll n;
	while(scanf("%lld",&n)&&n){
		int flag = 0;
		for(int i = 2; i <= 48; i++){
			if(n==f[i]){
				flag=1;
				break;
			}
		}
		if(flag) puts("Second win");
		else puts("First win");
	}
	return 0;
}

HDU 1564 稍加一些思維的博弈

一個n*n矩陣,一開始從左下角(任何一個角落)開始走  不能走走過的格子  問先手勝利還是後手勝利

我們畫出n*n的矩陣  並且講它進行分解

如果n爲偶數  我們可以把它分解成很多個1*2的方塊  先手每次走到包含當前格子的另外一個方塊上就行  此時先手必勝

如果n爲奇數  我們把起始點拿出來  剩下的格子也能分成很多 1*2的方塊 此時先手每次走入一個新的方塊 而後手走到這個方塊的另一個格子就行 此時後手必勝

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int main(){
	int n;
	while(~scanf("%d",&n)&&n){
		if(n%2==0) {
			puts("8600");
		}else{
			puts("ailyanlu");
		}
	}
	return 0;
}

 

HDU 1525

有兩個玩家,Stan 和 Ollie, 在玩遊戲。初始有兩個自然數。Stan是先手,每次把大的數字減去小的數字的任意倍數,但是不能使數字變成負數。然後Ollie進行同樣的操作,直到有一個玩家使一個數字變爲零。
例如,初始時數字爲(25,7):
25 7
11 7
4 7
4 3
1 3
1 0
這樣Stan贏

要掌握一種思維 如果我走一步可以讓下一個人達到必敗態 那我肯定是能贏的 這是顯然易見的

假設a>b 當a%b==0  顯然是當前操作的人勝利

當a>=2*b 也是當前操作的人勝利 爲什麼呢   我們可以看 a%b,b的狀態是必敗態還是必勝態  如果是必敗態 那麼我就走到這個狀態 讓下一個操作的人輸 否則我就走到 a%b+b,b這個狀態(是必敗的 因爲它只能走到一個必勝態a%b,b)

當a>b&&a<2*b時 進行一次簡單的操作(a=a%b,b=b) 然後更換一下當前操作的人就行了

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int main(){
	int a,b;
	while(scanf("%d%d",&a,&b)&&(a||b)){
		if(a<b) swap(a,b);
		bool flag=true;
		while(b){
			if(a%b==0||a>=2*b) break;
			a%=b;
			if(a<b) swap(a,b);
			flag^=1;
		}
		if(flag) puts("Stan wins");
		else puts("Ollie wins");
	}
	return 0;
}

巴什博弈變式  HDU - 2897

原來取的範圍是1~m 現在的範圍變成了 p~q了  

稍微推一下我們就發現  

1~p 先手必敗 p+1~p+q 先手必勝 p+q+1~2p+q 先手必敗 2p+q+1~2p+2q先手必勝 以此類推

也就是說每一個長度爲p+q的 前p個都是先手必敗態 後q個都是先手必勝態 

  

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
bool flag[65536+10];
int main(){
	int n,p,q;
	while(~scanf("%d%d%d",&n,&p,&q)){
		n%=(p+q);
		if(n>=1&&n<=p) puts("LOST");
		else puts("WIN");
	}
	return 0;
}

Nim博弈 HDU - 1850

Nim博弈是比較經典的博弈:桌子上有M堆撲克牌;每堆牌的數量分別爲ai (i=1…M);兩人輪流進行;每走一步可以任意選擇一堆並取走其中的任意張牌;桌子上的撲克全部取光,則遊戲結束;最後一次取牌的人爲勝者。
先手必勝當且僅當 a[1]^a[2]^a[3]....^a[m] != 0

我們可以這樣推導  

假設 a[1]^a[2]^a[3]....^a[m] = x, 那麼肯定存在一個牌堆 它的牌數目爲a[i]  並且a[i]^x<a[i]  (比如a[i]在x的最高位爲1) 

因此我們可以在 i 牌堆裏面取適當數目的牌 使得 a[i]->a[i]^x

到另外一個人取牌的時候  a[1]^a[2]^a[3]....^a[m] = 0, 根據異或的性質 無論他怎麼取 取完以後  a[1]^a[2]^a[3]....^a[m] != 0

由數學歸納法可知: a[1]^a[2]^a[3]....^a[m] != 0 爲必勝局面,一定存在一種行動讓對手面臨 “各堆牌異或起來等於0” 。 a[1]^a[2]^a[3]....^a[m] = 0 爲必敗局面,無論如何行動,都會讓對手面臨一個“各堆石子異或起來不等於0” 的必勝局面。

在來看這題  第一步能走的方案數  那肯定是 現求出 x  在遍歷a數組  如果 a[i]>a[i]^x 則方案數+1

#include<bits/stdc++.h>
using namespace std;
int a[102];
int main(){
	int n;
	while(scanf("%d",&n)&&n){
		int xo=0;
		for(int i = 1; i <= n; i++) scanf("%d",&a[i]),xo^=a[i];
		int sum=0;
		for(int i = 1; i <= n; i++)
			if((a[i]^xo)<a[i]) sum++;
		printf("%d\n",sum);
	}
	return 0;
}

威佐夫博弈 HDU - 1527 

有兩堆石子,數量任意,可以不同。遊戲開始由兩個人輪流取石子。遊戲規定,每次有兩種不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在兩堆中同時取走相同數量的石子。最後把石子全部取完者爲勝者。

規律是:假設a<b  如果  (int)(b-a)*(5^(0.5)+1)/2==a 那麼後手必勝 否則先手必勝

轉載了詳細證明:ICG博弈_威佐夫博弈(Wythoff Game)及證明

根據貝蒂定理 我們能求出 a=(\sqrt{5}+1)/2  b=(\sqrt5+3)/2  可以用來求威佐夫博弈的每一項

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int main(){
	int a,b;
	while(~scanf("%d%d",&a,&b)){
		if(a>b) swap(a,b);
		int k = (int)((b-a)*1.0*(sqrt(5)+1)/2.0);
		if(k==a) puts("0");
		else puts("1");
	}
	return 0;
}

鏡像原理 HDU - 3951 

鏡像原理:博弈論常用小技巧,當對方先操作時,我們可以模仿對方的操作來進行鏡像的操作以達到必勝的目的  

首先看一個簡單的題  一個長度爲n的序列(首尾不相連) A,B兩人輪流進行操作  每次可以取連續的k(k<=n) 個數  最後取完的人勝利  (ps:如果第3個數被取了 那麼2和4不能同時取) 

顯然 如果 k==1  我們可以通過n的奇偶性判斷輸贏  如果n&1先手勝利 否則後手勝利

接下來就是鏡像原理的應用了:

 k>=2的時候  先手可以從序列 居中的位置 取1個或2個連續的數 使得序列的左右兩端長度相同   進行完這個操作後 無論後手怎麼取 先手在對應的鏡像中取就行了 這樣先手是必勝的

 HDU - 3951  這題把序列首尾相連變成了環形  當先手取完一次以後就變成序列了  然後再按上述方法 後手可以必勝 另外注意判斷一下特殊情況就行

#include<bits/stdc++.h>
using namespace std;
int main(){
	int t,ca=0;
	scanf("%d",&t);
	while(t--){
		int n,k;
		scanf("%d%d",&n,&k);
		printf("Case %d: ",++ca);
		if(k>=n){
			puts("first");
			continue;
		}
		if(k==1&&n%2){
			puts("first");
			continue;
		}
		puts("second");
	}	
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章