Nim博弈-屬於組合遊戲
條件:
1、有兩名選手;
2、兩名選手交替對遊戲進行移動(move),每次一步,選手可以在(一般而言)有限的合法移動集合中任選一種進行移動;
3、對於遊戲的任何一種可能的局面,合法的移動集合只取決於這個局面本身,不取決於輪到哪名選手操作、以前的任何操作、骰子的點數或者其它什麼因素;
4、如果輪到某名選手移動,且這個局面的合法的移動集合爲空(也就是說此時無法進行移動),則這名選手負。根據這個定義,很多日常的遊戲並非ICG。例如象棋就不滿足條件3,因爲紅方只能移動紅子,黑方只能移動黑子,合法的移動集合取決於輪到哪名選手操作。
定義:
第一個命題顯然,terminal position只有一個,就是全0,異或仍然是0。
第二個命題,對於某個局面(a1,a2,...,an),若a1^a2^...^an<>0,一定存在某個合法的移動,將ai改變成ai'後滿足a1^a2^...^ai'^...^an=0。不妨設a1^a2^...^an=k,則一定存在某個ai,它的二進制表示在k的最高位上是1(否則k的最高位那個1是怎麼得到的)。這時ai^k<ai一定成立。則我們可以將ai改變成ai'=ai^k,此時a1^a2^...^ai'^...^an=a1^a2^...^an^k=0。
第三個命題,對於某個局面(a1,a2,...,an),若a1^a2^...^an=0,一定不存在某個合法的移動,將ai改變成ai'後滿足a1^a2^...^ai'^...^an=0。因爲異或運算滿足消去率,由a1^a2^...^an=a1^a2^...^ai'^...^an可以得到ai=ai'。所以將ai改變成ai'不是一個合法的移動。證畢。
根據這個定理,我們可以在O(n)的時間內判斷一個Nim的局面的性質,且如果它是N-position,也可以在O(n)的時間內找到所有的必勝策略。
2 45 45 3 3 6 9樣例輸出
No Yes
(2)如果輪到某人取火柴的時候,他能夠取完火柴,那麼此人贏,設爲N-格局
(3)如果輪到某人取火柴的時候,他能夠將當前格局轉化爲某個P格局,那麼此格局爲N-格局
(4)如果輪到某人取火柴的時候,他不能將當前格局轉化爲某個P格局,那麼此格局爲P-格局
下面我們來推導一個定理:一個格局記爲(x1,x2,...,xn),且次序無關,此格局爲 P-格局 當且僅當 x1^x2^...^xn = 0.其中^是按位異或運算
推導: 對應上述4種情形(爲了敘述的簡潔,並不十分嚴謹地證明,而是直接假設結論正確,再說明定理的工作機制)
(1)當(x1,x2,...,xn)中全爲0時 ,格局爲P-格局,此時x1 ^ x2 ^ ... ^ xn = 0成立。
(2)當(x1,x2,...,xn)中只有一個不爲0,格局爲N-格局,此時x1 ^ x2 ^ ... ^ xn = 0不成立。
(3)當(x1,x2,...,xn)是P-格局時,x1,...,xn不全爲0.(反證法)
假設x1 ^ x2 ^ ... ^ xn = p,且p不爲0,
記 p 的二進制表示中,最左的1(最高位的1)在從左至右數第 k 位.
由於p是異或運算的結果,那麼 x1, x2 , ... , xn中至少有一個數第k位爲1,
不妨設 xi 的第 k 位爲1,那麼 xi ^ p 第 k 位爲0,那麼xi > xi^p 顯然成立.
也就是說,存在某種取法,使i堆的火柴數變化到 xi^p .
題設x1 ^ x2 ^ ... ^ xn = p,那麼x1 ^ x2 ^ ... ^ xn ^ p = 0.
那麼當前格局可以轉化到某個P-格局,也就是說當前格局時N-格局,矛盾
所以,必有p=0.
(4)當x1 ^ x2 ^ ... ^ xn = 0時,如果存在某個移動 xi 變化到 xi ’ ,且x1^x2^....^xi ' ^...^xn = 0,
那麼由異或運算的消去律有,xi = xi ' ,也就是說一根火柴都沒取,這不允許的,所以
當前格局只能是P格局
#include <iostream>
#include <stdio.h>
using namespace std;
int pile[1010];
int main(){
int nc;
//cin>>nc;
scanf("%d",&nc);
while(nc--){
int M;scanf("%d",&M);
int result = 0;
for(int i=0; i<M; ++i)
{
scanf("%d",&pile[i]);
// cin>>pile[i];
result ^= pile[i];
}
if(result)
puts("PIAOYI\n");
//cout<<"PIAOYI"<<endl;
else
puts("HRDV\n");
// cout<<"HRDV" <<endl;
}
return 0;
}
取石子(九)
- 描述
-
最近TopCoder的Yougth和Hrdv在玩一個遊戲,遊戲是這樣的。
有n堆石子,兩個人輪流從其中某一堆中任意取走一定的石子,最後不能取的爲贏家,注意: 每次只能從一堆取任意個,可以取完這堆,但不能不取。
假設Yougth先取,輸入贏了的人名字、
- 輸入
- 第一行輸入n,代表有n組測試數據(n<=10000)
以下每組測試數據包含兩行:第一行:包含一個整數m,代表本組測試數據有m(m<=1000)堆石子;
:第二行:包含m個整數Ai(Ai<=10000),分別代表第i堆石子的數量。 - 輸出
- 若Yougth贏輸出“Yougth”,否則輸出“Hrdv”注意每組結果佔一行。。
- 樣例輸入
-
3 2 1 1 3 3 8 11 2 5 10
- 樣例輸出
-
Yougth Hrdv Yougth
首先給出結論: 先手勝當且僅當
(1)所有堆石子數都爲1且遊戲的result值爲0 ,(2)存在某堆石子數大於1且遊戲的result值不爲0
2。分兩種情況
a.只有一堆石子>1。你可以把它變成1。
b.至少兩隊石子>1,你只需要把SG值邊爲0就可以了,這個操作之後,至少還有兩堆石子>1,然後對方隨便怎麼操作,都會把SG變成非0
還需要證明1,2的反面是必敗的。
1顯然。
2你會把SG變成非0,而且因爲現在至少兩堆石子>1了,所以你還會給人家至少留一個>1的,那麼無論怎麼搞,都會送給後手一個必勝態。
#include<stdio.h>
int main()
{
int T,n,a,i,result,count;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
result=0;
count=0;
for(i=1; i<=n; i++)
{
scanf("%d",&a);
result=result^a;
if(a>1)
count++;
}
if((count&&result)||(!count&&!result))
printf("Yougth\n");
else
printf("Hrdv\n");
}
return 0;
}
現在我們不想研究到底先手爲勝還是爲負,我只想問大家:
——“先手的人如果想贏,第一步有幾種選擇呢?”
我們知道在Nim博弈中
如果我們面對的是一個非奇異局勢(a,b,c),要如何變爲奇異局勢呢?假設 a < b< c,我們只要將 c 變爲 a^b,即可,因爲有如下的運算結果: a^b^(a^b)=(a^a)^(b^b)=0^0=0。要將c 變爲a^b,只要從 c中減去 c-(a^b)即可。
也就是說,其中一個堆C必須大於所有其他所有堆異或的值,由此我們可以進行枚舉
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
int main()
{
int n,a[105],i,j,cnt;
while(~scanf("%d",&n),n)
{
cnt = 0;
for(i = 0; i<n; i++)
scanf("%d",&a[i]);
for(i = 0; i<n; i++)
{
int s = 0;
for(j = 0; j<n; j++)
{
if(i!=j)
s^=a[j];
}
if(a[i] > s)
cnt++;
}
printf("%d\n",cnt);
}
return 0;
}
3.輸出上述的第一步走法:
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
int a[200005],ans[200005][2];
int main()
{
int n,i,j,cnt,s;
while(~scanf("%d",&n),n)
{
cnt = 0;
s = 0;
for(i = 0; i<n; i++)
{
scanf("%d",&a[i]);
s^=a[i];
}
for(i = 0; i<n; i++)
{
if(a[i] > (s^a[i]))
{
ans[cnt][0] = a[i];
ans[cnt][1] = s^a[i];
cnt++;
}
}
if(cnt)
{
printf("Yes\n");
for(i = 0; i<cnt; i++)
printf("%d %d\n",ans[i][0],ans[i][1]);
}
else
printf("No\n");
}
return 0;
}
題目描述: 兩個人玩遊戲,在標有1,2,3,4,5...的格子上有一些棋子,規則是選一枚棋子移動,要求不能跨越棋子移動,必需向左移動(可以移動任意格),不能移動的就輸掉比賽。 下面是一個棋局的例子: +--+--+--+--+--+--+--+--+--+ |1x|2 |3x|4 |5 |6x|7x|8 |9 |(旁邊標有x的表示在這裏有棋子) +--+--+--+--+--+--+--+--+--+ 給出棋子的初始位置,若先手勝,輸出"Georgia will win",否則輸出"Bob will win"(不含引號)。 輸入格式: 多組數據。 對於每組數據,第一行是有一個T,表示有T組數據。 對於每組數據,第一行有一個N,表示有N枚棋子。 接下來的一行有N個數,分別爲每個棋子的位置。 輸出格式: 對每組數據,輸出一行,如題目描述那樣。 樣例輸入: 2 3 1 2 3 8 1 5 6 7 9 12 14 17 樣例輸出: Bob will win Georgia will win
#include<iostream>
#include<algorithm>
using namespace std;
int main(){
int t,n,sum,position[10005];
cin>>t;
while(t--){
cin>>n;
position[0]=0;
for(int i=1;i<=n;i++)
cin>>position[i];
sort(position,position+n+1);
sum=0;
for(int i=n;i>0;i-=2){
sum=sum^(position[i]-position[i-1]-1);
}
if(sum==0)
cout<<"Bob will win"<<endl;
else cout<<"Georgia will win"<<endl;
}
//system("pause");
return 0;
}
節目大略步驟爲:先用糧食(一般是稻米)在地上“畫”出若干級臺階(表示N級浮屠)。又有若干小和尚隨機地“站”在某個臺階上。最高一級臺階必須站人,其它任意。(如圖1所示)
兩位參加遊戲的法師分別指揮某個小和尚向上走任意多級的臺階,但會被站在高級臺階上的小和尚阻擋,不能越過。兩個小和尚也不能站在同一臺階,也不能向低級臺階移動。
兩法師輪流發出指令,最後所有小和尚必然會都擠在高段臺階,再也不能向上移動。輪到哪個法師指揮時無法繼續移動,則遊戲結束,該法師認輸。
對於已知的臺階數和小和尚的分佈位置,請你計算先發指令的法師該如何決策才能保證勝出。
#include <stdio.h>
int main()
{
int a[105],b[105],i=0,j,k,count,sum;
char c;
while(1)
{
scanf("%d%c",&a[i++],&c);
if(c=='\n')
break;
}
count=i;
for(i = 0;i < count-1;i++)
b[i]=a[i+1]-a[i]-1;
b[count-1]=0;
sum=b[0];
for(i = 2;i < count;i = i+2)
sum^=b[i];
if(sum == 0)
printf("-1\n");
else
{
for(i = 0;i < count;i++)
for(j = 1;j <= b[i];j++)
{
b[i] -= j;
if(i!=0)
b[i-1]+=j;
sum = b[0];
for(k = 2;k < count;k = k+2)
sum ^= b[k];
if(sum == 0)
{
printf("%d %d\n",a[i],a[i]+j);
break;
}
b[i] +=j;
if(i != 0)
b[i-1] -= j;
}
}
return 0;
}