今天讲了博弈论的相关知识,感觉很注重思维和模型积累的是SG函数,应该是需要多做题才可以熟练掌握。
聚聚帮忙列出了几种应该掌握的博弈模型:1、巴什博弈 2、威佐夫博弈 3、斐波那契博弈 4、尼姆博弈 5、阶梯博弈 6\Nim博弈的第二种形式 7、树上博弈
找到了两篇很不错的关于博弈论知识的博客,并且学习了里面的一些姿势:
1、http://www.cnblogs.com/ECJTUACM-873284962/p/6398385.html
2、https://www.cnblogs.com/ECJTUACM-873284962/p/6921829.html
下来总结今日学习的题目:
A - Playing With Stones
题意:n堆stones,一次最多只能取走一半stones,stone为1时失败,先手是否胜利。
Thinking:
1、打表法找出SG函数的规律。 2、用亦或表示P-N状态。 3、利用多组Nim博弈的结论进行判断。
1 #include <cstdio>
2 #include <cstring>
3 #define LL long long
4 const int maxn = 1e3+10;
5 /*
6 int SG[maxn], S[maxn];
7 void getSG(int n){
8 memset(SG, 0, sizeof(SG));
9 //sg[0]=0; sg[1]=0;
10 for(int i=2; i<=n; i++){
11 memset(S, 0, sizeof(S));
12 for(int j=1; j*2<=i; j++){
13 S[SG[i-j]] = 1;
14 }
15 for(int j=0; ;j++){
16 if(!S[j]){
17 SG[i] = j;
18 break;
19 }
20 }
21 }
22 }
23 int main(){
24 getSG(100);
25 for(int i=1; i<=50; i++){
26 if(i%10==0) printf("\n");
27 printf("%d ",SG[i]);
28 }
29 return 0;
30 }
31 */
32 LL getsg(LL n){
33 return n%2 ? getsg(n/2) : n/2;
34 }
35 int main(){
36 int T,N;
37 scanf("%d",&T);
38 for(int t=0; t<T; t++){
39 scanf("%d",&N);
40 LL ans = 0;
41 for(int i=0; i<N; i++){
42 LL tmp;
43 scanf("%lld",&tmp);
44 ans ^= getsg(tmp);
45 }
46 if(ans){
47 printf("YES\n");
48 }else{
49 printf("NO\n");
50 }
51 }
52 return 0;
53 }
B - 悼念512汶川大地震遇难同胞——选拔志愿者
C - Public Sale
Thinking:
很明显的巴什博弈。
策略:一堆n个石子,最多取m个,最少1个,A先手。 n%(m+1)==0 :B胜; 否则A开始取n-n/(m+1)*(m+1)个石子,之后石子和始终为(m+1),即A胜。
1 #include <cstdio>
2 int main(){
3 int C;
4 scanf("%d",&C);
5 for(int c=0; c<C; c++){
6 int n,m;
7 scanf("%d%d",&n, &m);
8 if(n%(m+1) == 0){
9 printf("Rabbit\n");
10 }else{
11 printf("Grass\n");
12 }
13 }
14 return 0;
15 }
1 #include <cstdio>
2 int main(){
3 int n,m;
4 while( scanf("%d%d",&m,&n) != EOF){
5 if(m%(n+1) == 0){
6 printf("none\n");
7 }else if(m <= n){
8 for(int i=m; i<=n; i++){
9 printf("%d%s",i,i==n?"\n":" ");
10 }
11 }else{
12 printf("%d\n",m-(m/(n+1))*(n+1));
13 }
14 }
15 return 0;
16 }
D - No Gambling
认真读题就好了
题意:
给定两个N*N由红点和蓝点构成的完全对称的图,两个人轮流连边,每次只能对相邻同颜色两点连一条边且不能穿过对方所连的线,先手对蓝点连边,从左到右获胜,后手对红点连边,从上到下获胜,问谁胜谁负。
1 #include <cstdio>
2 int main(){
3 int n;
4 while(scanf("%d",&n) && (n+1)){
5 printf("I bet on Oregon Maple~\n");
6 }
7 return 0;
8 }
E - Good Luck in CET-4 Everybody!
Thinking:
开始产生错误思路:看到2的幂,想到利用数的二进制和位运算来判断,但发现当n=21=1+4+16时答案错误。
正解:sg打表找规律。关于规律为3的倍数先手胜利的思考:这是保证最后的1由先手来取,先手操作时始终保持留给对手的是3的倍数即可。
1 /*
2 int f[11];
3 bool sg[1005];
4 void init(){
5 f[0]=1;
6 for(int i=1; i<=10; i++) f[i] = f[i-1]*2;
7 for(int i=1; i<=1000; i++){
8 for(int j=0; f[j]<=i; j++){
9 if(!sg[i-f[j]]){
10 sg[i]=true;
11 break;
12 }
13 }
14 }
15 }
16 */
17 #include <cstdio>
18 int main(){
19 int n;
20 while(scanf("%d",&n) != EOF){
21 if(n%3==0){
22 printf("Cici\n");
23 }else{
24 printf("Kiki\n");
25 }
26 }
27 return 0;
28 }
F - Coin Game
题意: 将给定的n个硬币排成一圈,两人轮流取硬币,每次只能取连续的1到k个硬币,问先手胜还是负。
如果能够把游戏分成对称的两部分,那么先手在某一部分里做了某个操作,后手就可以在另一部分里做和它同样的操作。最后肯定是先手先结束他自己的那一部分,后手再结束另一部分,那么后手就胜利了。
1 #include <cstdio>
2 int main(){
3 int T;
4 scanf("%d",&T);
5 for(int t=1; t<=T; t++){
6 int n,k;
7 scanf("%d%d",&n, &k);
8 if(k==1 && (n%2==1)){
9 printf("Case %d: first\n",t);
10 }else if(n <= k){
11 printf("Case %d: first\n",t);
12 }else{
13 printf("Case %d: second\n",t);
14 }
15 }
16 return 0;
17 }
G - 取石子游戏
斐波那契博弈。
1 /*
2 #include <bits/stdc++.h>
3 using namespace std;
4 const int maxn = 1e3+10;
5 int sg[maxn][maxn];
6 int dfs(int x, int y){
7 if(sg[x][y] != -1) return sg[x][y];
8 set<int> s;
9 s.clear();
10 for(int i=1; i<=y; i++){
11 if(x-i >= 0){
12 s.insert(sg[x-i][2*i]);
13 }
14 }//属于这个集合的非负整数
15 for(int i=0; ;i++){
16 if(s.count(i) == 0){
17 sg[x][y] = i;
18 break;
19 }
20 }//不属于这个集合的最小非负整数 //这里用set()//A题用的是类似与hash的方法
21 return sg[x][y];
22 }
23 int main()
24 {
25 memset(sg, -1, sizeof(sg));
26 for(int i=1; i<=1000; i++){
27 sg[0][i] = 0;
28 sg[1][i] = 1;
29 }
30 for(int i=2; i<=100; i++){
31 for(int j=1; j<=100; j++){
32 sg[i][j] = dfs(i, j);
33 }
34 }
35 for(int i=1; i<=30; i++){
36 for(int j=1; j<=30; j++){
37 printf("%2d ",sg[i][j]);
38 }
39 cout << endl;
40 }
41 bool flag = true;
42 for(int i=1; i<=100; i++){
43 for(int j=1; j<i; j++){
44 if(sg[i][j]!=0){
45 flag=false; break;
46 }
47 }
48 if(flag){
49 cout << i << endl;
50 flag = true;
51 }else{
52 flag = true;
53 }
54 }
55 return 0;
56 }
57 */
58 #include <bits/stdc++.h>
59 using namespace std;
60 #define LL long long
61 const int maxn = 50;
62 LL f[maxn];
63 void fib(){
64 f[1]=1; f[2]=2;
65 for(int i=3; i<maxn; i++){
66 f[i] = f[i-1] + f[i-2];
67 }
68 }
69 int main(){
70 fib();
71 LL n;
72 while(scanf("%lld",&n) != EOF && (n!=0)){
73 if(lower_bound(f, f+50, n)-f>=50 || f[lower_bound(f, f+50, n)-f]!=n){
74 printf("First win\n");
75 }else{
76 printf("Second win\n");
77 }
78 }
79 return 0;
80 }
H - Nim or not Nim?
题意:Alice和Bob轮流取石子,每一次可以从任意一堆中拿走任意个石子,也可将一堆石子分为两个小堆。求必胜策略。
SG函数打表找规律。
1 #include <cstdio>
2 #include <cstring>
3 /*
4 const int N = 1e6+5;
5 int vis[N], sg[N];
6 void getSG(){
7 memset(sg, 0, sizeof(sg));
8 sg[0] = 0;
9 sg[1] = 1;
10 for(int i=1; i<100; i++){
11 memset(vis, 0, sizeof(vis));
12 for(int j=1; j<=i; j++){
13 vis[sg[i-j]] = 1;
14 }//移走任意个石子
15 for(int j=1; j<i; j++){
16 vis[sg[j]^sg[i-j]] = 1;
17 } //将石子分为两堆
18 for(int j=0; ; j++){
19 if(!vis[j]){
20 sg[i] = j;
21 break;
22 }
23 }
24 printf("%d %d\n", i, sg[i]);
25 }
26 }
27 */
28 int getsg(int n){
29 if(n%4==3) return n+1;
30 else if(n%4==0) return n-1;
31 else return n;
32 }
33 int main(){
34 int T;
35 scanf("%d", &T);
36 for(int t=0; t<T; t++){
37 int N,ans = 0;
38 scanf("%d", &N);
39 for(int i=0; i<N; i++){
40 int tmp;
41 scanf("%d", &tmp);
42 ans ^= getsg(tmp);
43 }
44 if(ans == 0){
45 printf("Bob\n");
46 }else{
47 printf("Alice\n");
48 }
49 }
50 return 0;
51 }
K - Being a Good Boy in Spring Festival
题意:有m堆牌,两个人先后取某堆中的任意(不少于一)张牌,最后取完者胜;问先手取胜第一次取牌有多少种取法。
Thinking:
利用亦或的性质,可以看成是自反性吧,然后逆着思考 必胜的情况下 首次操作 可以对哪些堆,可以保证亦或和为0;并且要符合实际,即 所取数量<=已有数量(此题小于也可以过,=应该更严谨吧)
1 #include <cstdio>
2 int a[105];
3 int main(){
4 int m;
5 while( scanf("%d",&m)!=EOF && m ){
6 int ans = 0;
7 for(int i=0; i<m; i++){
8 scanf("%d",&a[i]);
9 ans ^= a[i];
10 }
11 if(ans == 0){
12 printf("0\n");
13 }else{
14 int num=0;
15 for(int j=0; j<m; j++){
16 if((ans^a[j]) <= a[j]){
17 num++;
18 }
19 }
20 printf("%d\n",num);
21 }
22 }
23 return 0;
24 }
L - 取(m堆)石子游戏
Nim博弈,与K相似。
1 #include <cstdio>
2 const int maxm = 200005;
3 int a[maxm];
4 int main(){
5 int m;
6 while( scanf("%d",&m)!=EOF && m ){
7 int ans=0;
8 for(int i=0; i<m; i++){
9 scanf("%d", &a[i]);
10 ans ^= a[i];
11 }
12 if(ans == 0){
13 printf("No\n");
14 }else{
15 printf("Yes\n");
16 for(int i=0; i<m; i++){
17 int tmp = ans^a[i];
18 if(tmp <= a[i]){
19 printf("%d %d\n", a[i], tmp);
20 }
21 }
22 }
23 }
24 return 0;
25 }
M - 取石子游戏
Thinking:
很直接的威佐夫博弈,由此题可知积累模型的重要性,否则,这个题打表肉眼恐怕很难找到规律。
1 #include <cstdio>
2 #include <cmath>
3 int main(){
4 int a,b;
5 while(scanf("%d%d", &a, &b)!=EOF){
6 if(a > b){
7 int t=a; a=b; b=t;
8 }
9 int i = b-a;
10 b = (int)((sqrt(5.0)+1.0)/2.0*i);
11 if(a==b) printf("0\n");
12 else printf("1\n");
13 }
14 return 0;
15 }