巴什博弈 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;
}
有兩個玩家,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)及證明
根據貝蒂定理 我們能求出 可以用來求威佐夫博弈的每一項
#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;
}