取石子(一)php
基础的巴什博奕ios
巴什博奕的重点是只有一堆,数组
若是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时必输。spa
#include <iostream> 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"<<endl; }else{ cout<<"Lose"<<endl; } } return 0; }
取石子(二).net
这题是尼姆博弈和巴什博奕的结合code
每一堆是巴什博奕,若是巴什博奕赢了就当作是石子数为1的堆,若是输了就当作是石子数为0的堆(看作没有了),下面面对的就是尼姆博弈,若是1的堆数是2的倍数,就至关于尼姆博弈中的奇异局势,面对这种局势必输,若是石子数为1的堆数不是2的倍数,那么是非奇异局势,必赢。blog
#include <iostream> 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"<<endl; else cout<<"Lose"<<endl; } return 0; }
真是看了半天,好多帖子都没讲清楚的感受,男人八题之一,看上去就很是吓人,可是代码真的很简洁,有点像尼姆博弈。
可是不同,咱们能够分析,
假若有一堆石子,先手必赢(N局势),
假若有两堆石子,若是两堆的数量同样,先手必输(P局势),若是两堆数量不同,N局势,
假若有三堆石子,先手必赢(N局势)
假若有四堆石子,若是石子数量量相同,P,若是不相同,N。
以此类推
若是有n堆石子,n为奇数,先手必赢,若是n为偶数,且石子数两两相同,先手必输,不然就赢。
我本身想确定想不出来的感受……
#include<iostream> #include<cstring> 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"<<endl; else{ for(int i = 0; i <= 100; i++){ if(a[i] & 1){//若是不能被2除断 flag = 1; break; } } if(flag) cout<<"Win"<<endl; else cout<<"Lose"<<endl; } } return 0; }
普通的威佐夫博弈题,公式有俩,若是符合公式就是奇异局势,面对奇异局势必输,然而公式我还没看懂。先记下来
ak = (int)k * (1 + sqrt(5)/2)
bk = ak + k
而后代码就是套第一个公式,若是符合就输定了
#include <iostream> #include <algorithm> #include <cmath> 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"<<endl; }else cout<<"1"<<endl; } return 0; }
普通的斐波那契博弈
http://www.javashuo.com/article/p-kibsjrcj-hz.html证实过程在这里
结论:若是n是斐波那契数先手必败
#include <iostream> 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"<<endl; else cout<<"Yes"<<endl; } return 0; }
这题就是简单的尼姆博弈,可是要用scanf,或者优化后的cin才能过
#include <iostream> #include <cstdio> 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"<<endl; printf("PIAOYI\n"); else //cout<<"HRDV"<<endl; printf("HRDV\n"); } return 0; }
能够一个一个的分析,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 <iostream> using namespace std; int main(){ int n; while(cin>>n){ if(n == 1 || n == 2){ cout<<"Hrdv"<<endl; }else cout<<"Yougth"<<endl; } return 0; }
威佐夫博弈这个博客讲的蛮好,而后这道题也是套公式就能够作出来的,注意最后要换行……我就是没换行而后WA了好多发。
#include <iostream> #include <cmath> 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<<"("<<a[i][0]<<","<<a[i][1]<<")"; } cout<<endl; } return 0; }
跟上面那个威佐夫博弈讲的同样,假如能够赢,分两种状况
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 <iostream> #include <cmath> 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"<<endl; }else{//取一次使其变成奇异局势 cout<<"1"<<endl; if(c == 0){ cout<<"0 0"<<endl; }else{ int bb = temp + c; if(bb <= b && temp <= a){ cout<<temp<<" "<<bb<<endl; } } for(int i = 1;i <= b; i++){ int min = i * (sqrt(5) + 1) / 2; int max = min + i; if(min > a){ break; } if(min == a && max < b){ cout<<min<<" "<<max<<endl; }else if(max == b && min < a){ cout<<min<<" "<<max<<endl; }else if(max == a && min < b){ cout<<min<<" "<<max<<endl; } } } } return 0; }
想不明白为何不能直接把异或后的结果做为判断依据,若是尼姆博弈是ans == 0先手输,这里改为先手赢不就行了,为何还要统计石子数大于1的堆数呢?
后来一看测试数据,假若有两堆石子,分别有2个石子,5个石子
先手取完一堆:不管是2仍是5,对手能够把剩下的那堆取的只剩一个,那么先手就输了
先手不取完:不管是在2仍是在5中取的只剩1个,对手均可以在另外一堆中取的只剩一个,先手也输
在这个例子中,即便2 ^ 5 != 0 先手也是必输的,
对于反尼姆博弈,能够这样分析
若是石子数都为1:堆数为偶数时,先手必输,为奇数时,先手必胜。
若是石子数都不为1:
堆数为偶数时,先手每一步,不论是在一堆中取的只剩一个,仍是都取完,后手均可以作一样的动做,最后先手是必输的。
堆数为奇数时,先手最后总能够留1个给后手,先手赢
因此,1.存在石子数大于1的堆,堆数为奇数(也就是异或结果为1)先手赢
2.石子数全为1,堆数为偶数(异或结果为0),先手赢。
#include <iostream> 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"<<endl; }else cout<<"Hrdv"<<endl; } return 0; }
学到了学到了
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 <iostream> #include <cstring> 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"<<endl; else cout<<"Hrdv"<<endl; } return 0; }
巴什博弈 | 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 |
总结完毕,若是有错漏请不吝指出哦~