博弈学习(未)

ACM博弈学习小结

2016年08月10日 00:54:52

阅读数:2280

一、心得体会

1.ACM博弈题,不会的时候觉得难于上青天,会的时候觉得没有比博弈更水的题了;

博弈题看到的第一眼觉得是难题,代码敲完顿觉水题。你可能花半个小时去找规律,然后仅花2分钟敲代码。

2.博弈是单人游戏,也可以说是自己跟自己玩,因为“双方都做出最优决策”这一点限制了,最后的结果不取决

于你是谁,不取决于你的智商,只取决于你面对的局面

3.局面,这是博弈里面最最最重要的东西!!!(所谓SG也是指这一局面的SG),博弈是一种不公平的游戏

因为游戏开始的时候已经结束了,影响你胜负的就是你所面对的局面,因为双方采取最优策略

故而局面必然会以双方当前对自己最优的路径走下去,所以结局已经确定了

4.当你面对一个局面的时候如何做出最优的决策呢?你一定是走到了最后一步才确定了胜负,所以当前的局面

往往需要从最终的局面逆推而来(也就是从一个已知胜负的局面一步步推导其他的局面,有了这样的思想,SG

也就不那么难理解了)

5.关于SG:

入门了博弈的人都知道,博弈里面常常用到一个重要的概念 -- SG。但是SG是什么?你去百度的话会有非常专业的解答,

但是那些所谓的专业绝对让人看的头疼。这里说说我所理解的博弈里面的SG(仅限博弈)

挑程里是这样解释SG值的:

          除 任意一步所能转移到的子局面的SG值以外最小非负整数

仔细体会一下这句话,你会发现,这里对SG值的定义是递归定义的!

当前局面的SG是什么呢?请先去找当前局面的子局面的SG值。

显然,递归是有一个边界的,SG是一种递归,那么它也是有边界的,

不难发现,它的边界是没有子局面的局面(也就无法再转移的局面)

什么样的局面没有子局面呢,也就是胜负已定的局面。在第4点说到,

当前局面的最优策略是从胜负已定的最终局面逆推来的,这里的SG其实也是

说了这些,那么SG到底是什么呢?

联想当年学习递归的一个例子:

f(n)  =   1         , n = 1

            f(n-1) +1  ,n > 1

这样一个函数是我们学习递归时的经典例子,你说这里的F到底是什么?其实它不过是一个函数而已。

SG也是一样,它只是一个函数而已,函数这个词翻译成英语再翻译成中文,就成了“功能、作用”

那么SG的作用是什么呢?

举一个最简单的例子:

有一堆石头数量为n,两个人轮流从石堆拿{a1,a2,a3,......,ak}个石头,先取完所有石头者胜。

根据前面说的,首先找胜负已定的局面,当n=0的时候,石头被拿完了,败态

那么sg[0] = 0表示面对0个石头的局面者败,然后根据sg的定义,我们可以求出其他局面的sg值

(为了使每种局面确保有可以转移的子局面,我们假设{a1,a2,a3......,ak}里面一定有1,例如假设没有1的话

,假设为{5,6,7}那么局面4没有可以转移的子局面,这样会出现平局的情况,我们后面再说平局)

这样可以求出所有局面的sg值,然后sg的作用出来了~

我们发现,若sg[x] = 0,那么x是败态,这其中很神奇,鶸也说不清楚,只说一下胜态败态的转移

(其实光理解的话可能还是不知道什么是SG,但是看了后面的题目就能理解了并知道怎么用SG找到游戏的胜态败态了)

6.胜态与败态:

之前说了,博弈里面,游戏开始的时候已经结束了,影响你胜负的就是你所面对的局面。

也就是说,这个局面觉得了你的胜负,我们称能让你走向胜利的局面称为胜态,也是必胜态,专业术语也叫P态(积极的英语单词怎么写?)

称让你走向失败的状态称为败态,也是必败态,专业也叫N态(消极的英语单词鶸也不会拼。。。)

有一个很显然的规律:

只要当前状态可以转移到的状态中有一个是败态,那么当前状态就是胜态。

如果当前状态可以转移到的所有状态都是胜态,那么当前状态就是败态。

这两句话互为逆否命题,一眼就看出是对的就不解释了。

可以胜态败态的角度去理解下SG。

7.Nim游戏:

关于这个Nim游戏,百度的话又是一大堆乱七八糟看不下去的东西,

它的最原始的版本大概是说有N堆石头,{a1,a2,a3......,an}表示每堆的数量,两个人轮流选一个石堆拿若干石头(不能不拿),

如果轮到某个人时所有的石子堆都已经被拿空了,则判负。

这个游戏有个非常完美的结论:

令   s  =  a1^a2^a3....^an(^符号表示异或运算)

若 s = 0,则此局面为败态,否则为胜态

对于上面的式子,我们不难发现,当你从一个石堆拿走一些石头(即改变一个ai),一定会发生胜态和败态的转变

胜态一定会转移成败态,败态也一定有策略转移成胜态

当这个结论与SG结合,神奇的事发生

我们发现sg异或和为0的状态也是败态,否则胜态。

另外,很多游戏都可以转变为Nim的形式,例如POJ 1704(挑程上有讲解)

8.关于平局:

我们发现,一个必胜态的获得,必然是因为它可以转移到一个败态,那么是不是说相比于平局我们更倾向于败态呢?

如果有更多的败态,理论上可以转移出更多的胜态,但是孩子别太天真了啊~

博弈将“对敌人的仁慈就是对自己的残忍”这句话发挥的淋漓尽致,当你选择败态的时候,对方却不会傻傻按照你的想法给你转移胜态的

该你输的时候你还是得输,所以,在博弈里的决策,一定要是对自己最有利对对手最不利的策略才是最优策略,、

也就是说,如果实在不能赢,你一定宁可平局,也不要选择败态。例如今年HDU 多校题5754 里面马的情况

9.当初关于博弈看了很多但是都只是似懂非懂,只有做多了题才有更多的·体会

二、博弈做题技巧

做了个专题:点击打开博弈专题

题目其实好多都是做过的原题,不过以前都是自己找规律的,这次就是用SG打表找规律,通过这些题目也算是知道怎么使用SG找规律了

其中的题目大多都是打表找规律,不过也有一些有趣的题目

PS:题目选自kuangbin 的博弈分类:点击打开链接(难度的话,后面的题都蛮简单,前面的题稍难)

1.打表找规律题:

W - A multiplication game

输入n,从1开始,每次乘以2~9的数,谁最先达到n谁胜

直接上代码,其中solve()函数是打表的过程,找完规律之后直接解决不需要solve,不过为了记录自己的思路,打表的代码也保留了

 
  1. #include<iostream>

  2. #include<cstdio>

  3. #include<cstring>

  4. #include<set>

  5. #define mem(a,x) memset(a,x,sizeof(a))

  6. using namespace std;

  7. typedef long long ll;

  8. /*

  9. 败态:

  10. 10 - 18

  11. 163 - 324

  12. 2917 - 5832

  13. 综上:

  14. 败态:

  15. (9*18^i,18*18^i]

  16. i从0开始

  17. */

  18. const int N = 100000;

  19. int sg[N+4];

  20. void solve()

  21. {

  22. sg[1] = 0;

  23. for (int i = 2;i <= N;++i)

  24. {

  25. set<int>s;

  26. for (int j = 2;j <= 9;++j)

  27. {

  28. int to = i/j;

  29. if (i%j)to++;

  30. s.insert(sg[to]);

  31. }

  32. int g = 0;

  33. while (s.count(g)) ++g;

  34. sg[i] = g;

  35. }

  36. for (int i = 2000;i <= 9000;++i)

  37. {

  38. cout<<i<<" "<<sg[i]<<endl;

  39. }

  40. }

  41. ll l[10],r[10];

  42. void init()

  43. {

  44. l[0] = 9,r[0] = 18;

  45. for (int i = 1;i <= 9;++i)

  46. {

  47. l[i] = 18LL*l[i-1];

  48. r[i] = 18LL*r[i-1];

  49. }

  50. }

  51. bool loser(ll x)

  52. {

  53. for (int i = 0;i <= 9;++i)

  54. {

  55. if (x>l[i]&&x<=r[i]) return 1;

  56. }

  57. return 0;

  58. }

  59. int main()

  60. {

  61. // solve();

  62. ll n;init();

  63. while (~scanf("%I64d",&n))

  64. {

  65. if (loser(n)) puts("Ollie wins.");

  66. else puts("Stan wins.");

  67. }

  68. return 0;

  69. }

S - A Multiplication Game

S题和W题一样的,不说了

R - 悼念512汶川大地震遇难同胞——选拔志愿者

和S、W的意思也差不多,不过操作从乘法变成了加法,由于数据小,于是也没有找规律,直接打完所有表把规律存在表里就好

 

 
  1. #define mem(a,x) memset(a,x,sizeof(a))

  2. #include<iostream>

  3. #include<cstdio>

  4. #include<cstring>

  5. #include<algorithm>

  6. #include<queue>

  7. #include<set>

  8. #include<stack>

  9. #include<cmath>

  10. #include<map>

  11. #include<stdlib.h>

  12. #include<cctype>

  13. #include<string>

  14. #define Sint(n) scanf("%d",&n)

  15. #define Sll(n) scanf("%I64d",&n)

  16. #define Schar(n) scanf("%c",&n)

  17. #define Sint2(x,y) scanf("%d %d",&x,&y)

  18. #define Sll2(x,y) scanf("%I64d %I64d",&x,&y)

  19. #define Pint(x) printf("%d",x)

  20. #define Pllc(x,c) printf("%I64d%c",x,c)

  21. #define Pintc(x,c) printf("%d%c",x,c)

  22. using namespace std;

  23. typedef long long ll;

  24. const int N = 10010;

  25. bool sg[N+4];

  26. int n,m;

  27. void turn(int n)//以败态转移

  28. {

  29. for (int i = 1;i <= m;++i)

  30. {

  31. sg[n+i] = 1;//胜态

  32. }

  33. }

  34. void solve()

  35. {

  36. mem(sg,0);

  37. sg[0] = 0;

  38. for (int i = 0;i <= n;++i)

  39. {

  40. if (sg[i] == 0)//败态

  41. {

  42. turn(i);

  43. }

  44. }

  45. }

  46. int main()

  47. {

  48. int T;cin>>T;

  49. while (T--)

  50. {

  51. cin>>n>>m;

  52. solve();

  53. if (sg[n]) puts("Grass");

  54. else puts("Rabbit");

  55. }

  56. return 0;

  57. }

 

O - Calendar Game

同样从终态逆推,不过逆推的过程有点麻烦,导致看起来都像模拟了。。。

 
  1. #define mem(a,x) memset(a,x,sizeof(a))

  2. #include<iostream>

  3. #include<cstdio>

  4. #include<cstring>

  5. #include<algorithm>

  6. #include<queue>

  7. #include<set>

  8. #include<stack>

  9. #include<cmath>

  10. #include<map>

  11. #include<stdlib.h>

  12. #include<cctype>

  13. #include<string>

  14. #define Sint(n) scanf("%d",&n)

  15. #define Sll(n) scanf("%I64d",&n)

  16. #define Schar(n) scanf("%c",&n)

  17. #define Sint2(x,y) scanf("%d %d",&x,&y)

  18. #define Sll2(x,y) scanf("%I64d %I64d",&x,&y)

  19. #define Pint(x) printf("%d",x)

  20. #define Pllc(x,c) printf("%I64d%c",x,c)

  21. #define Pintc(x,c) printf("%d%c",x,c)

  22. using namespace std;

  23. typedef long long ll;

  24. bool isleap(int y)

  25. {

  26. if (y%400==0||(y%4==0&&y%100!=0)) return 1;

  27. else return 0;

  28. }

  29. struct Date

  30. {

  31. int y,m,d;

  32. Date()

  33. {

  34. y = 2001,m = 11,d = 4;

  35. }

  36. Date(int y,int m,int d):y(y),m(m),d(d){}

  37. bool operator == (const Date &a) const

  38. {

  39. return y==a.y&&m==a.m&&d==a.d;

  40. }

  41. bool operator < (const Date &a) const

  42. {

  43. if (y==a.y)

  44. {

  45. if (m == a.m) return d<a.d;

  46. return m<a.m;

  47. }

  48. return y<a.y;

  49. }

  50. Date sub()

  51. {

  52. d--;

  53. if (d == 0)

  54. {

  55. --m;

  56. if (m == 0)

  57. {

  58. m = 12;

  59. d = 31;

  60. y--;

  61. }

  62. else if (m==2)

  63. {

  64. if (isleap(y)) d = 29;

  65. else d = 28;

  66. }

  67. else if (m==1||m==3||m==5||m==7||m==8||m==10||m==12) d = 31;

  68. else d = 30;

  69. }

  70. return *this;

  71. }

  72. } ;

  73. map<Date,int>sg;

  74. bool ok(Date x)

  75. {

  76. int m = x.m;

  77. int d = x.d;

  78. if (m == 2)

  79. {

  80. if (isleap(x.y)) return d<=29;

  81. else return d<=28;

  82. }

  83. else if (m==1||m==3||m==5||m==7||m==8||m==10||m==12) return d<=31;

  84. else return d <= 30;

  85. }

  86. void output(Date d)

  87. {

  88. cout<<d.y<<" "<<d.m<<" "<<d.d<<endl;

  89. }

  90. void turn(Date n)

  91. {

  92. Date s = n;

  93. sg[s.sub()] = 1;

  94. Date t = n;

  95. t.m--;

  96. if (ok(t)) sg[t] = 1;

  97. }

  98. void moni()

  99. {

  100. Date d;sg.clear();

  101. sg[d] = 0;//败

  102. // output(d);

  103. Date s(1900,1,1);

  104. for (Date i;;i.sub())

  105. {

  106. // cout<<i.y<<" "<<i.m<<" "<<i.d<<endl;

  107. if (sg[i] == 0) turn(i);

  108. if (i == s) break;

  109. }

  110. }

  111. int main()

  112. {

  113. moni();

  114. int T;cin>>T;

  115. while (T--)

  116. {

  117. Date n;

  118. Sint2(n.y,n.m);Sint(n.d);

  119. if (sg[n]) puts("YES");

  120. else puts("NO");

  121. }

  122. return 0;

  123. }

 

 

V - Digital Deletions

题意是对于一个数字形式的字符串,可以把每一位的数字变小(包括0,不为负),可以删去一个0以及0右边的所有数一起删除,两人轮流操作

谁移除最后一个数胜

同样逆推局面推出胜态败态

逆推的时候操作变成将数字变大,或者在后面补0及其他数字,因为长度不超过6,所以还是很简单的

 

 
  1. #include<iostream>

  2. #include<cstdio>

  3. #include<cstring>

  4. #include<string>

  5. #include<set>

  6. #include<sstream>

  7. #include<map>

  8. #define mem(a,x) memset(a,x,sizeof(a))

  9. using namespace std;

  10. typedef long long ll;

  11. const int N = 1000000;

  12. bool sg[N+4];

  13. int dig[10];

  14. int getdig(int x)

  15. {

  16. int len = 0;

  17. while (x)

  18. {

  19. dig[++len] = x%10;

  20. x /= 10;

  21. }

  22. return len;

  23. }

  24. int turntonum(int bit[],int n)

  25. {

  26. int num = 0;

  27. for (int i = 1,j = 1;i <= n;++i,j*=10)

  28. {

  29. num += bit[i]*j;

  30. }

  31. return num;

  32. }

  33. void solve(int n,int i)

  34. {

  35. if (i == 1)

  36. {

  37. n*=10;

  38. for (int j = 0;j <= 9;++j)

  39. {

  40. // cout<<n+j<<endl;

  41. sg[n+j] = 1;

  42. }

  43. }

  44. else if (i == 2)

  45. {

  46. n*=100;

  47. for (int j = 0;j <= 99;++j)

  48. {

  49. // cout<<n+j<<endl;

  50. sg[n+j] = 1;

  51. }

  52.  
  53. }

  54. else if (i == 3)

  55. {

  56. n*=1000;

  57. for (int j = 0;j <= 999;++j)

  58. {

  59. // cout<<n+j<<endl;

  60. sg[n+j] = 1;

  61. }

  62.  
  63. }

  64. else if (i == 4)

  65. {

  66. n*=10000;

  67. for (int j = 0;j <= 9999;++j)

  68. {

  69. // cout<<n+j<<endl;

  70. sg[n+j] = 1;

  71. }

  72.  
  73. }

  74. }

  75. void turn(int n)//n是必败态,所有n可以转移到的状态都是必胜态

  76. {

  77. int len = getdig(n);

  78. for (int i = 1;i <= len;++i) //数字变大

  79. {

  80. for (int j = dig[i]+1;j <= 9;++j)

  81. {

  82. int d[10];

  83. memcpy(d,dig,sizeof(d));

  84. d[i] = j;

  85. int x = turntonum(d,len);

  86. // cout<<x<<endl;

  87. sg[x] = 1;

  88. }

  89. }

  90. //加0加数

  91. if (len < 6)

  92. {

  93. n *= 10;//后面加个0

  94. sg[n] = 1;

  95. int d = 5-len;

  96. for (int i = 1;i <= d;++i)

  97. {

  98. solve(n,i);

  99. }

  100. }

  101. }

  102. void fool()

  103. {

  104. sg[0] = 1;

  105. // turn(1);

  106. for (int i = 1;i < N;++i)

  107. {

  108. if (sg[i] == 0) turn(i);

  109. }

  110. }

  111. int main()

  112. {

  113. fool();

  114. string s;

  115. while (cin>>s)

  116. {

  117. if (s[0] == '0') puts("Yes");

  118. else

  119. {

  120. stringstream ss(s);

  121. int n;

  122. ss>>n;

  123. if (sg[n]) puts("Yes");

  124. else puts("No");

  125. }

  126. }

  127. return 0;

  128. }

 

M - Play a game

大胆猜测,小心求证,自己随便玩几种局面就会发现奇败偶胜(代码略)

 

 
  1. int n;

  2. while (cin>>n)

  3. {

  4. if (!n) break;

  5. if (n&1) puts("ailyanlu");

  6. else puts("8600");

  7. }


L - Good Luck in CET-4 Everybody!

 

依旧简单打表找规律,自己手动找规律也可以,不过为了练习下SG的运用,还是用SG打表(也比手动找规律更快更准)

具体用SG打表找规律的方法代码中见:

 

 
  1. #define mem(a,x) memset(a,x,sizeof(a))

  2. #include<iostream>

  3. #include<cstdio>

  4. #include<cstring>

  5. #include<algorithm>

  6. #include<queue>

  7. #include<set>

  8. #include<stack>

  9. #include<cmath>

  10. #include<map>

  11. #include<stdlib.h>

  12. #include<cctype>

  13. #include<string>

  14. #define Sint(n) scanf("%d",&n)

  15. #define Sll(n) scanf("%I64d",&n)

  16. #define Schar(n) scanf("%c",&n)

  17. #define Sint2(x,y) scanf("%d %d",&x,&y)

  18. #define Sll2(x,y) scanf("%I64d %I64d",&x,&y)

  19. #define Pint(x) printf("%d",x)

  20. #define Pllc(x,c) printf("%I64d%c",x,c)

  21. #define Pintc(x,c) printf("%d%c",x,c)

  22. using namespace std;

  23. typedef long long ll;

  24. const int N = 1000;

  25. int sg[N+7];

  26. void fool()

  27. {

  28. sg[0] = 0;

  29. for (int i = 1;i <= N;++i)

  30. {

  31. set<int>s;

  32. s.insert(sg[i-1]);

  33. for (int j = 1;j <= 10;++j)

  34. {

  35. int to = i - (1<<j);

  36. if (to < 0) continue;

  37. s.insert(sg[to]);

  38. }

  39. int g = 0;

  40. while (s.count(g)) ++g;

  41. sg[i] = g;

  42. }

  43. for (int i = 1;i <= 70;++i)

  44. {

  45. if (sg[i] == 0) cout<<i<<endl;

  46. // cout<<i<<": "<<sg[i]<<endl;

  47. }

  48. }

  49. int main()

  50. {

  51. // fool();

  52. int n;

  53. while (cin>>n)

  54. {

  55. if (n%3==0)//败

  56. {

  57. puts("Cici");

  58. }

  59. else puts("Kiki");

  60. }

  61. return 0;

  62. }


K - kiki's game

打表找规律,发现当n和m都是奇数的时候必败,打表代码注释了没删除以供参考

 
  1. #define mem(a,x) memset(a,x,sizeof(a))

  2. #include<iostream>

  3. #include<cstdio>

  4. #include<cstring>

  5. #include<algorithm>

  6. #include<queue>

  7. #include<set>

  8. #include<stack>

  9. #include<cmath>

  10. #include<map>

  11. #include<stdlib.h>

  12. #include<cctype>

  13. #include<string>

  14. #define Sint(n) scanf("%d",&n)

  15. #define Sll(n) scanf("%I64d",&n)

  16. #define Schar(n) scanf("%c",&n)

  17. #define Sint2(x,y) scanf("%d %d",&x,&y)

  18. #define Sll2(x,y) scanf("%I64d %I64d",&x,&y)

  19. #define Pint(x) printf("%d",x)

  20. #define Pllc(x,c) printf("%I64d%c",x,c)

  21. #define Pintc(x,c) printf("%d%c",x,c)

  22. using namespace std;

  23. typedef long long ll;

  24. const int N = 100;

  25. int sg[N+4][N+4];

  26. bool ok(int x,int y)

  27. {

  28. return x>=1&&y>=1;

  29. }

  30. void fool()

  31. {

  32. sg[1][1] = 0;

  33. for (int i = 1;i <= 70;++i)

  34. {

  35. for (int j = 1;j <= 70;++j)

  36. {

  37. if (i == 1&&j == 1) continue;

  38. set<int>s;

  39. if (ok(i-1,j))

  40. {

  41. s.insert(sg[i-1][j]);

  42. }

  43. if (ok(i,j-1))

  44. {

  45. s.insert(sg[i][j-1]);

  46. }

  47. if (ok(i-1,j-1))

  48. {

  49. s.insert(sg[i-1][j-1]);

  50. }

  51. int g = 0;

  52. while(s.count(g)) ++g;

  53. sg[i][j] = g;

  54. }

  55. }

  56. for (int i = 1;i <= 20;++i)

  57. {

  58. for (int j = 1;j <= 20;++j)

  59. {

  60. if (sg[i][j] == 0)//败态

  61. {

  62. cout<<"("<<i<<","<<j<<")"<< endl;

  63. }

  64. }

  65. }

  66. }

  67. int main()

  68. {

  69. // fool();

  70. int n,m;

  71. while (cin>>n>>m)

  72. {

  73. if (n==0&&m==0) break;

  74. if ((n&1)&&(m&1)) puts("What a pity!");

  75. else puts("Wonderful!");

  76. }

  77. return 0;

  78. }

 

 

J - 取石子游戏

斐波那契博弈哦,必败态是斐波那契数

 

 
  1. #include<iostream>

  2. #include<cstdio>

  3. using namespace std;

  4. typedef long long ll;

  5. bool check(ll x)

  6. {

  7. ll f1 = 1,f2 = 1;

  8. ll f = 2;

  9. while (f <= x)

  10. {

  11. f = f1 + f2;

  12. if (f == x) return 1;

  13. f1 = f2;

  14. f2 = f;

  15. }

  16. return 0;

  17. }

  18. int main()

  19. {

  20. ll n;

  21. while (cin>>n)

  22. {

  23. if (!n) break;

  24. if (check(n)) puts("Second win");

  25. else puts("First win");

  26. }

  27. return 0;

  28. }


I - 邂逅明下

 

三个变量,找规律的时候不是那么容易,然后说到博弈还有一个特点就是,大胆猜测~

最后发现1~p必败,p+1~p+q必胜

 

 
  1. #define mem(a,x) memset(a,x,sizeof(a))

  2. #include<iostream>

  3. #include<cstdio>

  4. #include<cstring>

  5. #include<algorithm>

  6. #include<queue>

  7. #include<set>

  8. #include<stack>

  9. #include<cmath>

  10. #include<map>

  11. #include<stdlib.h>

  12. #include<cctype>

  13. #include<string>

  14. #define Sint(n) scanf("%d",&n)

  15. #define Sll(n) scanf("%I64d",&n)

  16. #define Schar(n) scanf("%c",&n)

  17. #define Sint2(x,y) scanf("%d %d",&x,&y)

  18. #define Sll2(x,y) scanf("%I64d %I64d",&x,&y)

  19. #define Pint(x) printf("%d",x)

  20. #define Pllc(x,c) printf("%I64d%c",x,c)

  21. #define Pintc(x,c) printf("%d%c",x,c)

  22. using namespace std;

  23. typedef long long ll;

  24. const int N = 100000;

  25. int sg[N+4];

  26. int n,p,q;

  27. void turn(int n)

  28. {

  29. for (int i = p;i <= q;++i)

  30. {

  31. sg[i+n] = 1;

  32. }

  33. }

  34. void fool()

  35. {

  36. mem(sg,0);

  37. sg[0] = 1;

  38. for (int i = 1;i <= n;++i)

  39. {

  40. if (sg[i] == 0) turn(i);

  41. }

  42. for (int i = 1;i <= n;++i)

  43. {

  44. if (sg[i] == 0) cout<<"{"<<i<<"}"<<endl;

  45. }

  46. }

  47. void solve()

  48. {

  49. for (int i = 1;i <= 10;++i)

  50. {

  51. for (int j = i;j <= 10;++j)

  52. {

  53. p = i,q = j;

  54. n = 80;

  55. cout<<p<<","<<q<<":"<<endl;

  56. fool();

  57. cout<<"----------------------------------"<<endl;

  58. }

  59. }

  60. }

  61. int main()

  62. {

  63. // solve();

  64. while (Sint(n) == 1)

  65. {

  66. Sint2(p,q);//fool();

  67. // if (sg[n]) puts("WIN");

  68. // else puts("LOST");

  69. --n;

  70. n%=(p+q);

  71. if (n < p) puts("LOST");

  72. else puts("WIN");

  73. }

  74. return 0;

  75. }

 

 

E - Fliping game

找规律,发现当右下角是1的时候必胜

插一句,这个游戏公平吗?是公平的,因为右下角是1的概率是1/2,而其他的石头怎么样不需要考虑^_^

 

 
  1. #define mem(a,x) memset(a,x,sizeof(a))

  2. #include<iostream>

  3. #include<cstdio>

  4. #include<cstring>

  5. #include<algorithm>

  6. #include<queue>

  7. #include<set>

  8. #include<stack>

  9. #include<cmath>

  10. #include<map>

  11. #include<stdlib.h>

  12. #include<cctype>

  13. #include<string>

  14. #define Sint(n) scanf("%d",&n)

  15. #define Sll(n) scanf("%I64d",&n)

  16. #define Schar(n) scanf("%c",&n)

  17. #define Sint2(x,y) scanf("%d %d",&x,&y)

  18. #define Sll2(x,y) scanf("%I64d %I64d",&x,&y)

  19. #define Pint(x) printf("%d",x)

  20. #define Pllc(x,c) printf("%I64d%c",x,c)

  21. #define Pintc(x,c) printf("%d%c",x,c)

  22. using namespace std;

  23. typedef long long ll;

  24.  
  25. int main()

  26. {

  27. int T;Sint(T);

  28. while(T--)

  29. {

  30. int n,m;

  31. Sint2(n,m);

  32. int s ;

  33. for (int i = 0;i < n;++i)

  34. {

  35. for (int j = 0;j < m;++j)

  36. {

  37. Sint(s);

  38. }

  39. }

  40. if (s) puts("Alice");

  41. else puts("Bob");

  42. }

  43. return 0;

  44. }

 

 

2.Nim 游戏变形:

U - John

 

Nim游戏的简单变形,特判全部是1的情况:如果全部是1,奇败偶胜,否则就按Nim游戏的异或和为0的是败态

 
  1. #define mem(a,x) memset(a,x,sizeof(a))

  2. #include<iostream>

  3. #include<cstdio>

  4. #include<cstring>

  5. #include<algorithm>

  6. #include<queue>

  7. #include<set>

  8. #include<stack>

  9. #include<cmath>

  10. #include<map>

  11. #include<stdlib.h>

  12. #include<cctype>

  13. #include<string>

  14. #define Sint(n) scanf("%d",&n)

  15. #define Sll(n) scanf("%I64d",&n)

  16. #define Schar(n) scanf("%c",&n)

  17. #define Sint2(x,y) scanf("%d %d",&x,&y)

  18. #define Sll2(x,y) scanf("%I64d %I64d",&x,&y)

  19. #define Pint(x) printf("%d",x)

  20. #define Pllc(x,c) printf("%I64d%c",x,c)

  21. #define Pintc(x,c) printf("%d%c",x,c)

  22. using namespace std;

  23. typedef long long ll;

  24. //const int N = 100;

  25. //int a[N];

  26. int main()

  27. {

  28. int n;

  29. int T;cin>>T;

  30. while (T--)

  31. {

  32. int s = 0;cin>>n;

  33. bool allone = 1;

  34. for (int i = 1,x;i <= n;++i)

  35. {

  36. Sint(x);

  37. s ^= x;

  38. if (x > 1) allone = 0;

  39. }

  40. if (allone)//奇败偶胜

  41. {

  42. if (n&1) puts("Brother");

  43. else puts("John");

  44. }

  45. else

  46. {

  47. if (s) puts("John");

  48. else puts("Brother");

  49. }

  50.  
  51. }

  52. return 0;

  53. }


T - Be the Winner

和上面一题一样的规律,完全不一样的游戏却有完全一样的规律

 
  1. #define mem(a,x) memset(a,x,sizeof(a))

  2. #include<iostream>

  3. #include<cstdio>

  4. #include<cstring>

  5. #include<algorithm>

  6. #include<queue>

  7. #include<set>

  8. #include<stack>

  9. #include<cmath>

  10. #include<map>

  11. #include<stdlib.h>

  12. #include<cctype>

  13. #include<string>

  14. #define Sint(n) scanf("%d",&n)

  15. #define Sll(n) scanf("%I64d",&n)

  16. #define Schar(n) scanf("%c",&n)

  17. #define Sint2(x,y) scanf("%d %d",&x,&y)

  18. #define Sll2(x,y) scanf("%I64d %I64d",&x,&y)

  19. #define Pint(x) printf("%d",x)

  20. #define Pllc(x,c) printf("%I64d%c",x,c)

  21. #define Pintc(x,c) printf("%d%c",x,c)

  22. using namespace std;

  23. typedef long long ll;

  24.  
  25. int main()

  26. {

  27. int n;

  28. while (cin>>n)

  29. {

  30. int s = 0;bool allone = 1;

  31. for (int i = 1,x;i <= n;++i)

  32. {

  33. Sint(x);

  34. s^=x;

  35. if (x > 1) allone = 0;

  36. }

  37. if (allone)//奇败偶胜

  38. {

  39. if (n&1) puts("No");

  40. else puts("Yes");

  41. }

  42. else

  43. {

  44. if (s) puts("Yes");

  45. else puts("No");

  46. }

  47. }

  48. return 0;

  49. }

H - Nim or not Nim?

和今年多校里面的一道博弈题基本一样,规律基本都是一样的,这里是可以把石头分两堆,今年多校的那题(HDU 5795)分三堆一样的原理

直接打表找规律,打表的过程注释以供参考

 
  1. #define mem(a,x) memset(a,x,sizeof(a))

  2. #include<iostream>

  3. #include<cstdio>

  4. #include<cstring>

  5. #include<algorithm>

  6. #include<queue>

  7. #include<set>

  8. #include<stack>

  9. #include<cmath>

  10. #include<map>

  11. #include<stdlib.h>

  12. #include<cctype>

  13. #include<string>

  14. #define Sint(n) scanf("%d",&n)

  15. #define Sll(n) scanf("%I64d",&n)

  16. #define Schar(n) scanf("%c",&n)

  17. #define Sint2(x,y) scanf("%d %d",&x,&y)

  18. #define Sll2(x,y) scanf("%I64d %I64d",&x,&y)

  19. #define Pint(x) printf("%d",x)

  20. #define Pllc(x,c) printf("%I64d%c",x,c)

  21. #define Pintc(x,c) printf("%d%c",x,c)

  22. using namespace std;

  23. typedef long long ll;

  24. //const int N = 1000;

  25. //int sg[N+6];

  26. //int SG(int st)

  27. //{

  28. // if (sg[st]!=-1) return sg[st];

  29. // set<int>s;

  30. // s.insert(SG(0));

  31. // for (int i = 1;i < st;++i)

  32. // {

  33. // s.insert(SG(st-i));//拿

  34. // s.insert(SG(i)^SG(st-i) );//分

  35. // }

  36. // int g = 0;

  37. // while (s.count(g)) ++g;

  38. // sg[st] = g;

  39. // return sg[st];

  40. //}

  41. //void solve()

  42. //{

  43. // mem(sg,-1);

  44. // sg[0] = 0;

  45. // for (int i = 1;i <= 50;++i)

  46. // {

  47. // cout<<i<<" :"<<SG(i)<<endl;

  48. // }

  49. //}

  50. int SG(int st)

  51. {

  52. if (st == 0) return 0;

  53. if (st%4==0) return st-1;

  54. if (st%4==3) return st+1;

  55. return st;

  56. }

  57. int main()

  58. {

  59. // solve();

  60. int T;cin>>T;

  61. while (T--)

  62. {

  63. int n;

  64. Sint(n);

  65. int s = 0;

  66. for (int i = 1,x;i <= n;++i)

  67. {

  68. Sint(x);

  69. s ^= SG(x);

  70. }

  71. if (s) puts("Alice");

  72. else puts("Bob");

  73. }

  74. return 0;

  75. }

PS:另附HDU 5795代码对比:(表打出来了规律就很简单了)

 
  1. #define mem(a,x) memset(a,x,sizeof(a))

  2. #include<iostream>

  3. #include<cstdio>

  4. #include<cstring>

  5. #include<algorithm>

  6. #include<queue>

  7. #include<set>

  8. #include<stack>

  9. #include<cmath>

  10. #include<map>

  11. #include<stdlib.h>

  12. #include<cctype>

  13. #include<string>

  14. #define Sint(n) scanf("%d",&n)

  15. #define Sll(n) scanf("%I64d",&n)

  16. #define Schar(n) scanf("%c",&n)

  17. #define Sint2(x,y) scanf("%d %d",&x,&y)

  18. #define Sll2(x,y) scanf("%I64d %I64d",&x,&y)

  19. #define Pint(x) printf("%d",x)

  20. #define Pllc(x,c) printf("%I64d%c",x,c)

  21. #define Pintc(x,c) printf("%d%c",x,c)

  22. using namespace std;

  23. typedef long long ll;

  24. //const int N = 1000;

  25. //int sg[N+5];

  26. //int SG(int st)

  27. //{

  28. // if (sg[st]!=-1) return sg[st];

  29. // set<int>s;

  30. // s.insert(SG(0));

  31. // for (int i = 1;i < st;++i)

  32. // {

  33. // s.insert(SG(st-i));//拿

  34. // for (int j = 1;j+i < st;++j)

  35. // {

  36. // s.insert(SG(i)^SG(j)^SG(st-i-j));//分

  37. // }

  38. // }

  39. // int g = 0;

  40. // while (s.count(g)) ++g;

  41. // sg[st] = g;

  42. // return sg[st];

  43. //}

  44. //void solve()

  45. //{

  46. // mem(sg,-1);

  47. // sg[0] = 0;

  48. // for (int i = 1;i <= 50;++i)

  49. // {

  50. // cout<<i<<": "<<SG(i)<<endl;

  51. // }

  52. //}

  53. int SG(int st)

  54. {

  55. if (st == 0) return 0;

  56. if (st%8 == 0) return st-1;

  57. if (st%8 == 7) return st+1;

  58. return st;

  59. }

  60. int main()

  61. {

  62. // solve();

  63. int T;cin>>T;

  64. while (T--)

  65. {

  66. int n;

  67. Sint(n);

  68. int s = 0;

  69. for (int i = 1,x;i <= n;++i)

  70. {

  71. Sint(x);

  72. s^=SG(x);

  73. }

  74. if (s) puts("First player wins.");

  75. else puts("Second player wins.");

  76. }

  77. return 0;

  78. }

 

F - Daizhenyang's Coin

我以为算是找规律的题,不过找的不是十进制数的规律,而是二进制数的规律,本来博弈就和二进制有着密不可分的关系

所以找规律的时候也要记得考虑一下二进制(这一点不仅是博弈,记得很多其他地方也用到找二进制数的规律)

不过有文章专门讲解了这一类型的游戏的策略:博弈-翻硬币游戏

这里的规律是如果x的二进制里面1个数为奇数,sg[x]就是2x,否则是2x+1

关于unique去重函数:点击打开链接

 
  1. #define mem(a,x) memset(a,x,sizeof(a))

  2. #include<iostream>

  3. #include<cstdio>

  4. #include<cstring>

  5. #include<algorithm>

  6. #include<queue>

  7. #include<set>

  8. #include<stack>

  9. #include<cmath>

  10. #include<map>

  11. #include<stdlib.h>

  12. #include<cctype>

  13. #include<string>

  14. #define Sint(n) scanf("%d",&n)

  15. #define Sll(n) scanf("%I64d",&n)

  16. #define Schar(n) scanf("%c",&n)

  17. #define Sint2(x,y) scanf("%d %d",&x,&y)

  18. #define Sll2(x,y) scanf("%I64d %I64d",&x,&y)

  19. #define Pint(x) printf("%d",x)

  20. #define Pllc(x,c) printf("%I64d%c",x,c)

  21. #define Pintc(x,c) printf("%d%c",x,c)

  22. using namespace std;

  23. typedef long long ll;

  24. int getone(int x)//返回二进制1的个数

  25. {

  26. int t = 0;

  27. while (x)

  28. {

  29. if (x&1) ++t;

  30. x>>=1;

  31. }

  32. return t;

  33. }

  34. int SG(int x)

  35. {

  36. if (getone(x)&1) return 2*x;

  37. else return 2*x+1;

  38. }

  39. ll a[111];

  40. int main()

  41. {

  42. int n;

  43. while (cin>>n)

  44. {

  45. ll s = 0;

  46. for (int i = 0;i < n;++i)

  47. {

  48. Sll(a[i]);

  49. }

  50. sort(a,a+n);

  51. n = unique(a,a+n)-a;

  52. for (int i = 0;i < n;++i)

  53. {

  54. s ^= SG(a[i]);

  55. }

  56. if (!s) puts("Yes");

  57. else puts("No");

  58. }

  59. return 0;

  60. }

 

3.状态转移:

Q - Being a Good Boy in Spring Festival

一般博弈都是问当前的局面是胜态还是败态,这个问如果是胜态,第一步有几种走法

真正理解博弈的会明白,博弈双方对局面做出的转移

当某人面对胜态的时候,他会将胜态转移成败态,

而面对败态的人不管怎么操作,只能将局面由败态转为胜态(不包含平局)

这是因为,如果异或和为0(败态)不管怎么操作都将使异或和变为非0(胜态)

而异或和不为0(胜态),一定有策略将异或和变为0(败态)

所以这题就是找,如果面对的是异或和不为0的胜态,有多少种方案将其变成异或和为0的败态

关于异或,有个很有用的性质:a^a^b = b  (即相同的数异或为0),具体操作看代码

 
  1. #define mem(a,x) memset(a,x,sizeof(a))

  2. #include<iostream>

  3. #include<cstdio>

  4. #include<cstring>

  5. #include<algorithm>

  6. #include<queue>

  7. #include<set>

  8. #include<stack>

  9. #include<cmath>

  10. #include<map>

  11. #include<stdlib.h>

  12. #include<cctype>

  13. #include<string>

  14. #define Sint(n) scanf("%d",&n)

  15. #define Sll(n) scanf("%I64d",&n)

  16. #define Schar(n) scanf("%c",&n)

  17. #define Sint2(x,y) scanf("%d %d",&x,&y)

  18. #define Sll2(x,y) scanf("%I64d %I64d",&x,&y)

  19. #define Pint(x) printf("%d",x)

  20. #define Pllc(x,c) printf("%I64d%c",x,c)

  21. #define Pintc(x,c) printf("%d%c",x,c)

  22. using namespace std;

  23. typedef long long ll;

  24. /*

  25. 将非0态(胜态)转化为0态(负态)有多少种方案

  26. */

  27. const int N = 100;

  28. int a[N+4];

  29. int s;

  30. bool ok(int x)

  31. {

  32. int ns = s^x;//把x变成更小的数,使状态变为0

  33. if (ns < x) return 1;

  34. else return 0;

  35. }

  36. int main()

  37. {

  38. int n;

  39. while (cin>>n)

  40. {

  41. if (!n) break;

  42. s = 0;

  43. for (int i = 1;i <= n;++i)

  44. {

  45. Sint(a[i]);

  46. s ^= a[i];

  47. }

  48. if (s == 0) puts("0");

  49. else

  50. {

  51. int sun = 0;

  52. for (int i = 1;i <= n;++i)

  53. {

  54. if (ok(a[i])) ++sun;

  55. }

  56. Pintc(sun,'\n');

  57. }

  58. }

  59. return 0;

  60. }

 

P - Public Sale

一样的水题打表,不过问的是第一次的选择有哪些,那么枚举第一次的选择,判断子局面是不是败态即可

(也就是只能将败态留给对手)

 
  1. #define mem(a,x) memset(a,x,sizeof(a))

  2. #include<iostream>

  3. #include<cstdio>

  4. #include<cstring>

  5. #include<algorithm>

  6. #include<queue>

  7. #include<set>

  8. #include<stack>

  9. #include<cmath>

  10. #include<map>

  11. #include<stdlib.h>

  12. #include<cctype>

  13. #include<string>

  14. #define Sint(n) scanf("%d",&n)

  15. #define Sll(n) scanf("%I64d",&n)

  16. #define Schar(n) scanf("%c",&n)

  17. #define Sint2(x,y) scanf("%d %d",&x,&y)

  18. #define Sll2(x,y) scanf("%I64d %I64d",&x,&y)

  19. #define Pint(x) printf("%d",x)

  20. #define Pllc(x,c) printf("%I64d%c",x,c)

  21. #define Pintc(x,c) printf("%d%c",x,c)

  22. using namespace std;

  23. typedef long long ll;

  24. const int N= 2111;

  25. int sg[N];

  26. int n,m;

  27. void turn(int n)

  28. {

  29. for (int i = 1;i <= m;++i)

  30. {

  31. sg[n+i] = 1;

  32. }

  33. }

  34. void fool()

  35. {

  36. mem(sg,0);

  37. sg[0] = 0;

  38. for (int i = 0;i <= n;++i)

  39. {

  40. if (sg[i] == 0) turn (i);

  41. }

  42. }

  43. bool ok(int x)//将此局面给 对手,对手能否赢

  44. {

  45. mem(sg,0);

  46. sg[x] = 0;

  47. for (int i = x;i <= n;++i)

  48. {

  49. if (sg[i] == 0) turn(i);

  50. }

  51. if (sg[n] == 0)//对手不能赢

  52. return 1;

  53. else return 0;

  54. }

  55. int main()

  56. {

  57. while (cin>>n>>m)//n 是成本,m是可以加的数

  58. {

  59. fool();

  60. if (sg[n] == 0) puts("none");

  61. else

  62. {

  63. bool first = 1;

  64. for (int i = 1;i <= m;++i)

  65. {

  66. if (ok(i))

  67. {

  68. if (first)

  69. {

  70. printf("%d",i);

  71. first = 0;

  72. }

  73. else printf(" %d",i);

  74. }

  75. }

  76. puts("");

  77. }

  78. }

  79. return 0;

  80. }

 

 

4.思维王道

N - Euclid's Game

这题的选择稍多,假设a<b,可以选择对b减去k*a,只要k*a<=b

这里涉及到一个自由度的概念,有些局面是固定的,比如(4,7),它只能按(4,7)-(4,3)-(1,3)的情况走下去

像这样的局面就是没有自由度,操作者只有唯一的选择

对于形如b-a<a的局面,就是没有自由度的局面,操作唯一,所以可以直接模拟

对于形如b-a>a的局面,其实这是必胜的局面

(不要问b-a==a的局面,b是a的倍数显然必胜态)

综合上述规律,直接模拟即可(详解参考挑程310面):

 
  1. #define mem(a,x) memset(a,x,sizeof(a))

  2. #include<iostream>

  3. #include<cstdio>

  4. #include<cstring>

  5. #include<algorithm>

  6. #include<queue>

  7. #include<set>

  8. #include<stack>

  9. #include<cmath>

  10. #include<map>

  11. #include<stdlib.h>

  12. #include<cctype>

  13. #include<string>

  14. #define Sint(n) scanf("%d",&n)

  15. #define Sll(n) scanf("%I64d",&n)

  16. #define Schar(n) scanf("%c",&n)

  17. #define Sint2(x,y) scanf("%d %d",&x,&y)

  18. #define Sll2(x,y) scanf("%I64d %I64d",&x,&y)

  19. #define Pint(x) printf("%d",x)

  20. #define Pllc(x,c) printf("%I64d%c",x,c)

  21. #define Pintc(x,c) printf("%d%c",x,c)

  22. using namespace std;

  23. typedef long long ll;

  24. bool moni(int a,int b)

  25. {

  26. bool win = 1;

  27. while(1)

  28. {

  29. if (a>b) swap(a,b);

  30. if (b%a == 0) break;

  31. if (b-a>a) break;

  32. b -= a;

  33. win = !win;

  34. }

  35. return win;

  36. }

  37. int main()

  38. {

  39. int a,b;

  40. while (cin>>a>>b)

  41. {

  42. if (a==0&&b==0) break;

  43. if ( moni(a,b)) puts("Stan wins");

  44. else puts("Ollie wins");

  45. }

  46. return 0;

  47. }


G - Game

非常神奇,和二分图也联系起来了,想清楚了就是Nim游戏变形

 
  1. #define mem(a,x) memset(a,x,sizeof(a))

  2. #include<iostream>

  3. #include<cstdio>

  4. #include<cstring>

  5. #include<algorithm>

  6. #include<queue>

  7. #include<set>

  8. #include<stack>

  9. #include<cmath>

  10. #include<map>

  11. #include<stdlib.h>

  12. #include<cctype>

  13. #include<string>

  14. #define Sint(n) scanf("%d",&n)

  15. #define Sll(n) scanf("%I64d",&n)

  16. #define Schar(n) scanf("%c",&n)

  17. #define Sint2(x,y) scanf("%d %d",&x,&y)

  18. #define Sll2(x,y) scanf("%I64d %I64d",&x,&y)

  19. #define Pint(x) printf("%d",x)

  20. #define Pllc(x,c) printf("%I64d%c",x,c)

  21. #define Pintc(x,c) printf("%d%c",x,c)

  22. using namespace std;

  23. typedef long long ll;

  24. /*

  25. 1. 分成一个二分图

  26. <span style="white-space:pre"> </span>如果可以从A拿卡片到B,连一条从A到B的边。

  27. 把所有box编号x满足((x%3==0&&x%2==1) || x%3==1)这个条件的放左边,其他放右边,不难发现

  28. a) 只有从左边到右边的边或从右到左的边。

  29. b) 所有不能拿卡片出去的box都在左边。

  30. 2. 证明左边的box并不影响结果。假设当前从右边的局势来看属于输家的人为了

  31. 摆脱这种局面,从左边的某盒子A拿了n张卡片到B,因为B肯定有出去的边,对手

  32. 会从B再取走那n张卡片到左边,局面没有变化

  33. 3. 于是这就相当于所有右边的box在nim游戏。

  34. */

  35. int main()

  36. {

  37. int T;cin>>T;

  38. int kas = 0;

  39. while (T--)

  40. {

  41. int n;scanf("%d",&n);

  42. int s = 0;

  43. for (int i = 1,x;i <= n;++i)

  44. {

  45. scanf("%d",&x);

  46. // if ((i%2==1&&i%3==0)) continue;

  47. if ((i%3==0&&i%2==0)||i%3==2) s^=x;

  48. }

  49. printf("Case %d: ",++kas);

  50. if (s) puts("Alice");

  51. else puts("Bob");

  52. }

  53. return 0;

  54. }


B - Gems Fight!

局面的描述比较复杂,使用状态压缩博弈,一样的博弈原理,从终态去逆推当前面对的局面

另写了详细题解: HDU 4778 Gems Fight!(博弈+状压)

 

C - Mine

这个题才真正让人看到SG的作用,前面说当SG和Nim游戏的异或和的结论结合的时候可能并没有什么感觉

这题就很好的应用了这点,整个棋盘的sg就是每个格子的sg的异或和

另写了详细题解: HDU 4678 Mine (博弈SG+自由度原理)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值