取石子(一)
基础的巴什博奕
巴什博奕的重点是只有一堆,
如果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时必输。
#include
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"<
取石子(二)
这题是尼姆博弈和巴什博奕的结合
每一堆是巴什博奕,如果巴什博奕赢了就看成是石子数为1的堆,如果输了就看成是石子数为0的堆(看做没有了),下面面对的就是尼姆博弈,如果1的堆数是2的倍数,就相当于尼姆博弈中的奇异局势,面对这种局势必输,如果石子数为1的堆数不是2的倍数,那么是非奇异局势,必赢。
#include
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"<
取石子(三)
真是看了半天,好多帖子都没讲清楚的感觉,男人八题之一,看上去就非常吓人,但是代码真的很简洁,有点像尼姆博弈。
但是不一样,我们可以分析,
假如有一堆石子,先手必赢(N局势),
假如有两堆石子,如果两堆的数量一样,先手必输(P局势),如果两堆数量不一样,N局势,
假如有三堆石子,先手必赢(N局势)
假如有四堆石子,如果石子数量量相同,P,如果不相同,N。
以此类推
如果有n堆石子,n为奇数,先手必赢,如果n为偶数,且石子数两两相同,先手必输,否则就赢。
我自己想肯定想不出来的感觉……
#include
#include
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"<
取石子(四)
普通的威佐夫博弈题,公式有俩,如果符合公式就是奇异局势,面对奇异局势必输,然而公式我还没看懂。先记下来
ak = (int)k * (1 + sqrt(5)/2)
bk = ak + k
然后代码就是套第一个公式,如果符合就输定了
#include
#include
#include
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"<
取石子(五)
普通的斐波那契博弈
https://blog.csdn.net/dgq8211/article/details/7602807证明过程在这里
结论:如果n是斐波那契数先手必败
#include
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"<
取石子(六)
这题就是简单的尼姆博弈,但是要用scanf,或者优化后的cin才能过
#include
#include
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"<
取石子(七)
可以一个一个的分析,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
using namespace std;
int main(){
int n;
while(cin>>n){
if(n == 1 || n == 2){
cout<<"Hrdv"<
Wythoff Game
威佐夫博弈这个博客讲的蛮好,然后这道题也是套公式就可以做出来的,注意最后要换行……我就是没换行然后WA了好多发。
#include
#include
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<<"("<
取石子(八)
跟上面那个威佐夫博弈讲的一样,假如可以赢,分两种情况
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
#include
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"< a){
break;
}
if(min == a && max < b){
cout<
取石子(九)
想不明白为什么不能直接把异或后的结果作为判断依据,如果尼姆博弈是ans == 0先手输,这里改成先手赢不就好了,为什么还要统计石子数大于1的堆数呢?
后来一看测试数据,假如有两堆石子,分别有2个石子,5个石子
先手取完一堆:不论是2还是5,对手可以把剩下的那堆取的只剩一个,那么先手就输了
先手不取完:不论是在2还是在5中取的只剩1个,对手都可以在另一堆中取的只剩一个,先手也输
在这个例子中,即使2 ^ 5 != 0 先手也是必输的,
对于反尼姆博弈,可以这样分析
如果石子数都为1:堆数为偶数时,先手必输,为奇数时,先手必胜。
如果石子数都不为1:
堆数为偶数时,先手每一步,不管是在一堆中取的只剩一个,还是都取完,后手都可以做同样的动作,最后先手是必输的。
堆数为奇数时,先手最后总可以留1个给后手,先手赢
所以,1.存在石子数大于1的堆,堆数为奇数(也就是异或结果为1)先手赢
2.石子数全为1,堆数为偶数(异或结果为0),先手赢。
#include
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"<
取石子(十)
学到了学到了
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
#include
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"<
巴什博弈 | 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 |
总结完毕,如果有错漏请不吝指出哦~