博弈知识点
http://blog.csdn.net/acm_cxlove/article/details/7854526
简单的巴什博弈
1:HDOJ1846 Brave Game [找规律]
#include <iostream> #include <string.h> #include <stdio.h> using namespace std; int main() { int t; int n,m; scanf("%d",&t); while(t--) { scanf("%d%d",&n,&m); if(n%(m+1)==0) printf("second\n"); else printf("first\n"); } return 0; }
HDOJ2149 Public Sale [巴什博弈]输出走法
#include <iostream> #include <string.h> #include <stdio.h> using namespace std; int main() { int n,m; while(scanf("%d%d",&m,&n)!=EOF) { if(n>=m) { printf("%d",m); for(int i=m+1; i<=n; i++) printf(" %d",i); printf("\n"); } else { if(m%(n+1)==0) printf("none\n"); else printf("%d\n",m%(n+1)); } } return 0; }
威佐夫博弈
HDOJ1527&POJ1067 取石子游戏
#include <iostream> #include <string.h> #include <stdio.h> #include <math.h> using namespace std; int main() { int n,m; while(scanf("%d%d",&n,&m)!=EOF) { if(n<m) swap(n,m); int k=n-m; double r=((1+sqrt(5))/2.0); if(m!=(int)(k*r)) ///相等时就是先取者的奇异局面 就是输的状态
printf("1\n"); else printf("0\n"); } return 0; }
HDU 2177 较上次来说 这个让打印路径了~~~~~~~~~~
/* 由于要输出方案,便 得复杂了。数据不是很大,首先打表,所有whthoff 的奇异局势。 然后直接判断是否为必胜局面。、 如果必胜,首先判断能否直接同时相减得到。这里不需要遍历或者二分查找。由于两者同时减去一个数,他们的差不变,而且ak=k*(sqrt(5)+1),bk=ak+k; 则可以通过二者的差直接定位,然后判断。 对于另外一种情况,其中一个减去某个数,得到奇异局势,则是分情况二分查找。 注意一些细节 */ #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<vector> #include<string> #include<map> #define LL long long #define N 1000000 #define inf 1<<20 using namespace std; int a[500000],b[500000],cnt; void Search1(int n,int m){ int low=0,high=cnt,mid; while(low<=high){ mid=(high+low)>>1; if(n==a[mid]){ if(m>b[mid]) printf("%d %d\n",a[mid],b[mid]); return; } if(a[mid]<n) low=mid+1; else high=mid-1; } } void Search2(int n,int m){ int low=0,high=cnt,mid; while(low<=high){ mid=(high+low)>>1; if(n==b[mid]){ if(m>a[mid]) printf("%d %d\n",a[mid],b[mid]); return; } if(b[mid]<n) low=mid+1; else high=mid-1; } } int main(){ for(int i=0;i<500000;i++){ a[i]=(int)(i*(sqrt(5.0)+1)/2); b[i]=a[i]+i; if(b[i]>=1000000){ cnt=i; break; } } int n,m; while(scanf("%d%d",&n,&m)!=EOF&&n+m){ if(n>m) swap(n,m); if(n==(int)((sqrt(5.0)+1)*(m-n)/2)) printf("0\n"); else{ printf("1\n"); if(m-n<cnt&&n-a[m-n]==m-b[m-n]) printf("%d %d\n",a[m-n],b[m-n]); Search1(n,m); if(n!=m) Search1(m,n); Search2(n,m); if(n!=m) Search2(m,n); } } return 0; }
#include<iostream> #include<string.h> #include<math.h> using namespace std; int main() { int n, m; int k; double x=(1+sqrt(5.0))/2; while(scanf("%d%d",&n,&m)!=EOF && n+m) { if(n>m) //如果 n > m,根据奇异局势应该要a<b { n^=m; //通过三个异或 交换n、m m^=n; n^=m; } k=m-n; //显然,遇到奇异局势的人一定会输。 if((int)(k*x)==n) printf("0\n"); //因为奇异局势变成非奇异局势后,下一个人一定可以将局势 //再次变成奇异局势,所以一定还是第一次遇到奇异局势的人遇到奇异局势,则他定输 else { printf("1\n");//碰到非奇异局势,将其变成奇异局势,可能有两种变法。 for(int i=1;i<=n;i++)//①:同时从两堆中拿相同数目的石子。 { int a=n-i , b=m-i; k=b-a; //cout<<"a= "<<a<<" b= "<<b<<" "<<x<<endl; if((int)(k*x)==a) printf("%d %d\n",a, b); }//cout<<"111111111111"<<endl; for(int i=m-1;i>=0;i--)//②:从一堆中取。 { int a=n , b=i; if(a > b) //同理,保持奇异局势中 a < b { a^=b; b^=a; a^=b; } k=b-a; if((int)(k*x)==a) printf("%d %d\n",a , b); }//cout<<"22222222222222"<<endl; } } return 0; }
尼姆博奕
HDU1907
/* cnt == 0:说明每一种颜色都只有一个,相当于一堆石子,每次必须取一个的情况 Sg(x) = x % 2。 本题题义稍有不同,取走最后一个时是输,所以x = n - 1 如果cnt != 0,题义的改变对结果影响 */ #include <iostream> using namespace std; int value[101]; int main () { int n,sum,temp, t, i, cnt; cin>>t; while (t--) { cin>>n; sum = 0;cnt = 0; for(i = 0; i < n; i++) { cin>>temp; sum = sum^temp; if(temp > 1) cnt = 1; } if(cnt == 0) sum = ( (n-1) % 2 ); if(sum == 0) cout<<"Brother"<<endl; else cout<<"John"<<endl; } return 0; }
#include<iostream>
using namespace std;
int main()
{
int i,n,m;
int t;
cin>>t;
while(t--)
{
cin >> n;
int flag = 0; //判断是否是孤单堆
int s = 0;
for(i = 0; i < n; i++)
{
cin >> m;
s ^= m;
if(m > 1)
flag = 1;
}
if(flag == 0)
if(n % 2)
cout <<"Brother"<<endl;
else
cout <<"John"<<endl;
else if(s == 0)
cout << "Brother" <<endl;
else
cout << "John" <<endl;
}
return 0;
}
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; int main() { int t,n,i; int sum1,sum2,ans; int a[55]; scanf("%d",&t); while(t--) { scanf("%d",&n); sum1 = sum2 = ans = 0; for(i = 0;i<n;i++) { scanf("%d",&a[i]); ans^=a[i]; if(a[i]>=2) sum2++; else sum1++; } if((ans && sum2) || (!ans && !sum2)) printf("John\n"); if((ans && sum1%2 && !sum2) || (!ans && sum2>=2)) printf("Brother\n"); } return 0; }
HDU2509
Nim博弈变形(anti-nim)
题目大意:有n堆苹果,每堆有mi个。两人轮流取,每次可以从一堆苹果中取任意连续个苹果,最后取光者为输。Fra先下,问是否可以获胜。
算法如下:
这题与以往的博弈题的胜负条件不同,谁先走完最后一步谁输,但他也是一类Nim游戏,即为anti-nim游戏。
首先给出结论:先手胜当且仅当 ①所有堆石子数都为1且游戏的SG值为0(即有偶数个孤单堆-每堆只有1个石子数);②存在某堆石子数大于1且游戏的SG值不为0.
证明:
- 若所有堆都为1且SG值为0,则共有偶数堆石子,故先手胜。
-
i)只有一堆石子数大于1时,我们总可以对该石子操作,使操作后堆数为奇数且所有堆的石子数均为1;
ii)有超过一堆的石子数1时,先手将SG值变为0即可,且总还存在某堆石子数大于1
因为先手胜。
此题用到的概念:
【定义1】:若一堆中仅有一个石子,则被称为孤单堆。若大于1个,则称为充裕堆。
【定义2】:T态中,若充裕堆的堆数大于等于2,则称为完全利他态,用T2表示;若充裕堆的堆数等于0,则称为部分利他态。用T0表示。
孤单堆的根数异或智慧影响二进制的最后以为,但充裕堆会影响高位(非最后一位)。一个充裕堆,高位必有一位不为0,则所有根数异或不为0。故不会是T态。
【定理1】:S0态,即仅有奇数个孤单堆,必败。T0态必胜。
证明:S0态,其实就是每次只能取一根。每次第奇数根都由自己取,第偶数根都由对方取,所以最后一根必由自己取。所以必败。同理:T0态必胜。
【定理2】:S1态,只要方法正确,必胜。
证明:若此时孤单堆堆数为奇数,把充裕堆取完;否则,取成一根。这样,就变成奇数个孤单堆,由对方取。由定理1,对方必输,己必胜。
【定理3】:S2态不可转一次变为T0态。
证明:充裕堆数不可能一次由2变为0。
【定理4】:S2态可一次转变为T2态。
证明:因为对于任何一个S态,总能从一堆中取出若干个使之成为T态。又因为S1态,只要方法正确,必胜。S2态不可转一次变为T0态,所以转变的T态为T2态。
【定理5】:T2态,只能转变为S2态或S1态。
证明:因为T态,取任何一堆的若干根都将成为S态。由于充裕堆不可能一次由2变为0,所以此时的S态不可能为S0态。得证。
【定理6】:S2态,只要方法正确,必胜。
证明:方法如下:
- S2态,就把它变为T2态。(定理4);
-
对方只能T2转变为S2态或S1态(定理5)。
若转变为S2,则转向①。
若转变为S1,这时己必胜(定理1)。
【定理7】:T2态必输。
证明:同定理6.
综上所述:必输态有:T2、S0;必胜态有:S2、S1、T0。
#include<iostream> using namespace std; int main() { int i,n,m; while(cin >> n) { int flag = 0; //判断是否是孤单堆 int s = 0; for(i = 0; i < n; i++) { cin >> m; s ^= m; if(m > 1) flag = 1; } if(flag == 0) if(n % 2) cout << "No\n"; else cout << "Yes\n"; else if(s == 0) cout << "No" <<endl; else cout << "Yes" <<endl; } return 0; }
Nim博弈
HDU5011
Game
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 260 Accepted Submission(s): 202
● In the beginning of the game, there are a lot of piles of beads.
● Players take turns to play. Each turn, player choose a pile i and remove some (at least one) beads from it. Then he could do nothing or split pile i into two piles with a beads and b beads.(a,b > 0 and a + b equals to the number of beads of pile i after removing)
● If after a player's turn, there is no beads left, the player is the winner.
Suppose that the two players are all very clever and they will use optimal game strategies. Your job is to tell whether the player who plays first can win the game.
For each test case, the first line contains a postive integer n(n < 105) means there are n piles of beads. The next line contains n postive integer, the i-th postive integer ai(ai < 231) means there are ai beads in the i-th pile.
#include <iostream> #include <string.h> #include <stdio.h> using namespace std; int main() { int n; int a[20000]; while(scanf("%d",&n)!=EOF) { int sum=0; for(int i=0;i<n;i++) { scanf("%d",&a[i]); sum^=a[i]; } if(sum) printf("Win\n"); else printf("Lose\n"); } return 0; }
HDU 2167
取(m堆)石子游戏
Time Limit: 1000 MS Memory Limit: 32768 KB
64-bit integer IO format: %I64d , %I64u Java class name: Main
Description
Input
Output
Sample Input
2 45 45 3 3 6 9 5 5 7 8 9 10 0
Sample Output
No Yes 9 5 Yes 8 1 9 0 10 3
#include <iostream> #include <string.h> #include <stdio.h> #include <cmath> using namespace std; int main() { int n; int m[200005]; while(scanf("%d",&n),n) { int sum=0; for(int i=0;i<n;i++) { scanf("%d",&m[i]); sum=sum^m[i]; } if(sum) { printf("Yes\n"); int s=0; for(int i=0;i<n;i++) { s=sum^m[i]; if(s<m[i]) printf("%d %d\n",m[i],s); } } else printf("No\n"); } return 0; }