一.bash game
题目一般是这样:有一堆n个物品,两人轮流取,每次至少取一个,最多取m个,最后取光的人获胜。问如果你先取,你取多少就能获胜。
我们可以想一下,如果是(m+1)个物品,你无论取多少个,最后都是对方取完(最多取m个),比如,你取1,对方取m,你取2,对方取m-1,是不是无论你取多少个,对方都一定能赢。
那么反过来,我们是想赢的那个人,怎么反过来呢?如果我们能让物品为(m+1)+k个,我们第一次取k个,他第一次不论取多少,最后一次的我们都能取完。
这样的话,我们就让每个取的回合的物品都为m+1,然后就有这样的一个式子
n=(m+1)*r+s,其中r是回合数,如果s=0,那么我先取的话,对方一定赢,如果s>=1,那么我们一定能赢。
来道十分裸的例题:HDU 1846
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<vector>
#include<string>
using namespace std;
typedef long long ll;
const int maxn=1e6+10;
int main()
{
int t;
cin>>t;
while(t--)
{
int n,m;
cin>>n>>m;
int mod=n%(m+1);//这里的mod就是s
if(mod==0)
cout<<"second"<<endl;
else
cout<<"first"<<endl;
}
return 0;
}
二.Fibinacci's game
一共有n个石子,双方轮流取,规则:(1).先手不能在第一次把所有石子取完(2).之后每次可以取的石子数是从1到对手取的石子数的两倍之间(包含两倍)。
我们用到一个结论:如果石子总数n是斐波那契数那么后手赢,反之先手赢。(可以自己模拟一遍)
裸题是HDU 2516
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<vector>
#include<string>
using namespace std;
typedef long long ll;
const int maxn=1e6+10;
int main()
{
int fib[50]={0};
fib[0]=2;fib[1]=3;
for(int i=2;i<50;i++)
fib[i]=fib[i-1]+fib[i-2];
int n;
while(cin>>n&&n)
{
int flag=0;
for(int i=0;i<50;i++)
{
if(n==fib[i])
flag=1;
if(n<fib[i])
break;
}
if(flag==1)
puts("Second win");
else
puts("First win");
}
return 0;
}
三.Wythoff game
从两堆的若干个物品中,两人轮流从一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者胜。
这里我们会面对一个奇异局势,我们用(ak,bk)(ak<=bk,k=0,1,....,n),来表示两堆物品的数量并称其为局势,如果甲面对的是(0,0)那么甲一定输,或者甲面对(1,2),甲不论取走多少都会输,比如从a堆取1,从b堆取1,乙取完剩下;从a堆取1,乙取完剩下;从b堆取2,乙取完剩下;从b堆取1,乙取完剩下。我们把这样一种局势成为奇异局势。
像这样的奇异局势有(0,0),(1,2),(3,5),(4,7),(6,10),(8,13),(9,15),(10,17)......
可以看出a0=b0=0,ak是在前面未出现的最小自然数,比如(0,0)之后未出现的最小的是1,所以是(1,2),此时已经出现了0,1,2,所以下一个最小的自然数是3,就是(3,5)。bk=ak+k,b1=a1+1=1+1=2,所以是(1,2),b2=a2+2=3+2=5,所以是(3,5)。
如果面对非奇异局势,那么先拿者必胜,如果面对奇异局势,那么后拿者必胜。
有一个Betty定理:设a、b是正无理数且 1/a +1/b =1。记P={ [na] | n为任意的正整数},Q={ [nb] | n 为任意的正整数},([x]'指的是取x的整数部分)则P与Q是Z+的一个划分,即P∩Q=∅且P∪Q=Z+。
根据Betty定理我们知道上述矩阵中每一行第一列的数为[∅i],第二列的数为[(∅+1)i],其中∅=(sqrt(5)+1)/2为黄金分割比。
任给一个局势(a,b),怎么判断是不是奇异局势?如下:
ak=[k(1+√5 )/2],bk=ak+k(k=0,1,.....,n)
k=bk-ak;
如果ak=[(b-a)*(1+√5 )/2]那么就是奇异局势,否则不是。
裸题:POJ 1067
#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
using namespace std;
int main()
{
int a,b;
while(cin>>a>>b)
{
if(a>b)
swap(a,b);
int c=floor((b-a)*(sqrt(5.0)+1)/2);
if(a==c)
puts("0");
else
puts("1");
}
return 0;
}
四.Nimm game
有n堆若干个物品,两人任意从某一堆取任意多的物品,规定每次至少取一个,多者不限,最后取光者得胜。
我们用(a,b,c)表示某种局势,(0,0,0)显然是奇异局势,第二种是(0,n,n)只要与对手拿走同样多的物品,最后都会变成(0,0,0)的情况,所以也是奇异局势。(1,2,3)也是奇异局势,不论怎么拿都可以将它变成(0,n,n)的奇异局势。
怎么判断(x,y,z)是奇异局势呢?
借助异或运算。比如(1,2,3)
1 二进制 01,2的二进制是10,1和2异或是11,再和3 即11异或结果为0 0.
怎么让一个非奇异局势变成一个奇异局势呢?
比如假设(a,b,c)为非奇异局势,咱们用这么一个公式:a⊕ b⊕ (a⊕ b)=(a⊕ a)⊕ (b⊕ b)=0⊕ 0=0,那么我们把这个c变为a⊕ b,就能将(a,b,c)变为奇异局势,即变成c-(c-(a⊕ b))=c-c+(a⊕ b)=a⊕b.那么我们从c中拿多少才能变成奇异局势呢,即拿走c-(a⊕ b)。
例题HDU 1850(这道题是这样想的:如果你先手能赢则m个数异或的结果不为0,那肯定是存在拿走了某个数使之能成为一个奇异局势,假设n个数中存在一个数a,从a中拿走a-除a以外的那n-1个数的异或值,就能使之成为奇异局势,那么a一定是大于那n-1个数的异或值的,所以我们就找满足这样的a有多少个,就是你第一步可行的方案数)
#include<bits/stdc++.h>
using namespace std;
int a[110];
int main()
{
int m;
while(cin>>m&&m)
{
int ans=0,cnt=0;
for(int i=0;i<m;i++)
{
cin>>a[i];
ans^=a[i];
}
if(ans==0)
puts("0");
else
{
for(int i=0;i<m;i++)
{
int k=ans^a[i];
if(k<a[i])
cnt++;
}
cout<<cnt<<endl;
}
}
return 0;
}