ACM博弈学习小结

来源:互联网 发布:域名备案买什么服务器 编辑:程序博客网 时间:2024/06/05 11:06

一、心得体会

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,不过为了记录自己的思路,打表的代码也保留了
#include<iostream>#include<cstdio>#include<cstring>#include<set>#define mem(a,x) memset(a,x,sizeof(a))using namespace std;typedef long long ll;/*败态: 10 - 18 163 - 3242917 - 5832综上:败态:(9*18^i,18*18^i]i从0开始 */const int N = 100000; int sg[N+4];void solve(){sg[1] = 0; for (int i = 2;i <= N;++i){set<int>s;for (int j = 2;j <= 9;++j){int to = i/j;if (i%j)to++;s.insert(sg[to]); }int g = 0;while (s.count(g)) ++g;sg[i] = g;}for (int i = 2000;i <= 9000;++i) {cout<<i<<" "<<sg[i]<<endl;} }ll l[10],r[10];void init(){l[0] = 9,r[0] = 18;for (int i = 1;i <= 9;++i){l[i] = 18LL*l[i-1];r[i] = 18LL*r[i-1];}} bool loser(ll x){for (int i = 0;i <= 9;++i){if (x>l[i]&&x<=r[i]) return 1;}return 0;}int main(){//solve();ll n;init();while (~scanf("%I64d",&n)){if (loser(n)) puts("Ollie wins.");else puts("Stan wins.");}return 0;}

S - A Multiplication Game

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

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

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

#define mem(a,x) memset(a,x,sizeof(a))#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<queue>#include<set>#include<stack>#include<cmath>#include<map>#include<stdlib.h>#include<cctype>#include<string>#define Sint(n) scanf("%d",&n)#define Sll(n) scanf("%I64d",&n)#define Schar(n) scanf("%c",&n)#define Sint2(x,y) scanf("%d %d",&x,&y)#define Sll2(x,y) scanf("%I64d %I64d",&x,&y)#define Pint(x) printf("%d",x)#define Pllc(x,c) printf("%I64d%c",x,c)#define Pintc(x,c) printf("%d%c",x,c)using namespace std;typedef long long ll;const int N = 10010;bool sg[N+4];int n,m;void turn(int n)//以败态转移{for (int i = 1;i <= m;++i){sg[n+i] = 1;//胜态 }} void solve(){mem(sg,0);sg[0] = 0;for (int i = 0;i <= n;++i){if (sg[i] == 0)//败态{turn(i);} }}int main(){int T;cin>>T;    while (T--)    {cin>>n>>m;    solve();    if (sg[n]) puts("Grass");    else puts("Rabbit");}    return 0;}

O - Calendar Game
同样从终态逆推,不过逆推的过程有点麻烦,导致看起来都像模拟了。。。
#define mem(a,x) memset(a,x,sizeof(a))#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<queue>#include<set>#include<stack>#include<cmath>#include<map>#include<stdlib.h>#include<cctype>#include<string>#define Sint(n) scanf("%d",&n)#define Sll(n) scanf("%I64d",&n)#define Schar(n) scanf("%c",&n)#define Sint2(x,y) scanf("%d %d",&x,&y)#define Sll2(x,y) scanf("%I64d %I64d",&x,&y)#define Pint(x) printf("%d",x)#define Pllc(x,c) printf("%I64d%c",x,c)#define Pintc(x,c) printf("%d%c",x,c)using namespace std;typedef long long ll;bool isleap(int y){if (y%400==0||(y%4==0&&y%100!=0)) return 1;else return 0;}struct Date{int y,m,d;Date(){y = 2001,m = 11,d = 4;} Date(int y,int m,int d):y(y),m(m),d(d){} bool operator == (const Date &a) const{return y==a.y&&m==a.m&&d==a.d;}bool operator < (const Date &a) const{if (y==a.y){if (m == a.m) return d<a.d;return m<a.m;}return y<a.y;}Date sub(){d--;if (d == 0){--m;if (m == 0){m = 12;d = 31;y--;}else if (m==2){if (isleap(y)) d = 29;else d = 28;}else if (m==1||m==3||m==5||m==7||m==8||m==10||m==12) d = 31;else d = 30;}return *this;}} ;map<Date,int>sg;bool ok(Date x){int m = x.m;int d = x.d;if (m == 2) {if (isleap(x.y)) return d<=29;else return d<=28; }else if (m==1||m==3||m==5||m==7||m==8||m==10||m==12) return d<=31;else return d <= 30;}void output(Date d){cout<<d.y<<" "<<d.m<<" "<<d.d<<endl;}void turn(Date n){Date s = n;sg[s.sub()] = 1;Date t = n;t.m--;if (ok(t)) sg[t] = 1;}void moni(){Date d;sg.clear();sg[d] = 0;//败//output(d);Date s(1900,1,1);for (Date i;;i.sub()) {//cout<<i.y<<" "<<i.m<<" "<<i.d<<endl; if (sg[i] == 0) turn(i);if (i == s) break;}}int main(){    moni();    int T;cin>>T;    while (T--)    {    Date n;    Sint2(n.y,n.m);Sint(n.d);    if (sg[n]) puts("YES");    else puts("NO");}    return 0;}


V - Digital Deletions

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

谁移除最后一个数胜

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

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

#include<iostream>#include<cstdio>#include<cstring>#include<string>#include<set>#include<sstream>#include<map>#define mem(a,x) memset(a,x,sizeof(a))using namespace std;typedef long long ll;const int N = 1000000;bool sg[N+4];int dig[10];int getdig(int x){int len = 0;while (x){ dig[++len] = x%10;x /= 10;}return len;}int turntonum(int bit[],int n){int num = 0;for (int i = 1,j = 1;i <= n;++i,j*=10){num += bit[i]*j;}return num;}void solve(int n,int i){if (i == 1){n*=10;for (int j = 0;j <= 9;++j) {//cout<<n+j<<endl;sg[n+j] = 1;}}else if (i == 2){n*=100;for (int j = 0;j <= 99;++j) {//cout<<n+j<<endl;sg[n+j] = 1;}}else if (i == 3){n*=1000;for (int j = 0;j <= 999;++j){//cout<<n+j<<endl;sg[n+j] = 1;} }else if (i == 4){n*=10000;for (int j = 0;j <= 9999;++j) {//cout<<n+j<<endl;sg[n+j] = 1;}}}void turn(int n)//n是必败态,所有n可以转移到的状态都是必胜态 {int len = getdig(n);for (int i = 1;i <= len;++i) //数字变大 {for (int j = dig[i]+1;j <= 9;++j){int d[10];memcpy(d,dig,sizeof(d));d[i] = j;int x = turntonum(d,len);//cout<<x<<endl;sg[x] = 1;}}//加0加数if (len < 6) {n *= 10;//后面加个0sg[n] = 1;int d = 5-len;for (int i = 1;i <= d;++i){solve(n,i);}}} void fool(){sg[0] = 1;//turn(1);for (int i = 1;i < N;++i){if (sg[i] == 0) turn(i);}}int main(){fool();string s;while (cin>>s){if (s[0] == '0') puts("Yes");else {stringstream ss(s);int n;ss>>n;if (sg[n]) puts("Yes");else puts("No");}}return 0;}

M - Play a game

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

    int n;    while (cin>>n)    {    if (!n) break;    if (n&1) puts("ailyanlu");    else puts("8600");    }

L - Good Luck in CET-4 Everybody!

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

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

#define mem(a,x) memset(a,x,sizeof(a))#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<queue>#include<set>#include<stack>#include<cmath>#include<map>#include<stdlib.h>#include<cctype>#include<string>#define Sint(n) scanf("%d",&n)#define Sll(n) scanf("%I64d",&n)#define Schar(n) scanf("%c",&n)#define Sint2(x,y) scanf("%d %d",&x,&y)#define Sll2(x,y) scanf("%I64d %I64d",&x,&y)#define Pint(x) printf("%d",x)#define Pllc(x,c) printf("%I64d%c",x,c)#define Pintc(x,c) printf("%d%c",x,c)using namespace std;typedef long long ll;const int N = 1000;int sg[N+7];void fool(){sg[0] = 0;for (int i = 1;i <= N;++i){set<int>s;s.insert(sg[i-1]);for (int j = 1;j <= 10;++j){int to = i - (1<<j);if (to < 0) continue;s.insert(sg[to]);}int g = 0;while (s.count(g)) ++g;sg[i] = g;}for (int i = 1;i <= 70;++i){if (sg[i] == 0) cout<<i<<endl; //cout<<i<<": "<<sg[i]<<endl;}}int main(){//    fool();int n;while (cin>>n){if (n%3==0)//败{puts("Cici");} else puts("Kiki");}    return 0;}

K - kiki's game
打表找规律,发现当n和m都是奇数的时候必败,打表代码注释了没删除以供参考
#define mem(a,x) memset(a,x,sizeof(a))#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<queue>#include<set>#include<stack>#include<cmath>#include<map>#include<stdlib.h>#include<cctype>#include<string>#define Sint(n) scanf("%d",&n)#define Sll(n) scanf("%I64d",&n)#define Schar(n) scanf("%c",&n)#define Sint2(x,y) scanf("%d %d",&x,&y)#define Sll2(x,y) scanf("%I64d %I64d",&x,&y)#define Pint(x) printf("%d",x)#define Pllc(x,c) printf("%I64d%c",x,c)#define Pintc(x,c) printf("%d%c",x,c)using namespace std;typedef long long ll;const int N = 100;int sg[N+4][N+4];bool ok(int x,int y){return x>=1&&y>=1;}void fool(){sg[1][1] = 0;for (int i = 1;i <= 70;++i){for (int j = 1;j <= 70;++j){if (i == 1&&j == 1) continue;set<int>s;if (ok(i-1,j)){s.insert(sg[i-1][j]);}if (ok(i,j-1)){s.insert(sg[i][j-1]);}if (ok(i-1,j-1)){s.insert(sg[i-1][j-1]);}int g = 0;while(s.count(g)) ++g;sg[i][j] = g;}}for (int i = 1;i <= 20;++i){for (int j = 1;j <= 20;++j){if (sg[i][j] == 0)//败态{cout<<"("<<i<<","<<j<<")"<< endl;} }}} int main(){//    fool();int n,m;while (cin>>n>>m){if (n==0&&m==0) break;if ((n&1)&&(m&1)) puts("What a pity!");else puts("Wonderful!");}    return 0;}


J - 取石子游戏

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

#include<iostream>#include<cstdio>using namespace std;typedef long long ll;bool check(ll x){ll f1 = 1,f2 = 1;ll f = 2;while (f <= x){f = f1 + f2;if (f == x) return 1;f1 = f2;f2 = f;}return 0;}int main(){ll n;while (cin>>n){if (!n) break;if (check(n)) puts("Second win");else puts("First win");}return 0;} 

I - 邂逅明下

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

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

#define mem(a,x) memset(a,x,sizeof(a))#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<queue>#include<set>#include<stack>#include<cmath>#include<map>#include<stdlib.h>#include<cctype>#include<string>#define Sint(n) scanf("%d",&n)#define Sll(n) scanf("%I64d",&n)#define Schar(n) scanf("%c",&n)#define Sint2(x,y) scanf("%d %d",&x,&y)#define Sll2(x,y) scanf("%I64d %I64d",&x,&y)#define Pint(x) printf("%d",x)#define Pllc(x,c) printf("%I64d%c",x,c)#define Pintc(x,c) printf("%d%c",x,c)using namespace std;typedef long long ll;const int N = 100000;int sg[N+4];int n,p,q;void turn(int n){for (int i = p;i <= q;++i){sg[i+n] = 1;}}void fool(){mem(sg,0);sg[0] = 1;for (int i = 1;i <= n;++i){if (sg[i] == 0) turn(i);}for (int i = 1;i <= n;++i){if (sg[i] == 0) cout<<"{"<<i<<"}"<<endl;}}void solve(){for (int i = 1;i <= 10;++i){for (int j = i;j <= 10;++j){p = i,q = j;n = 80;cout<<p<<","<<q<<":"<<endl;fool();cout<<"----------------------------------"<<endl;}}}int main(){//solve();    while (Sint(n) == 1)    {    Sint2(p,q);//fool();//    if (sg[n]) puts("WIN");//    else puts("LOST");--n;n%=(p+q);if (n < p) puts("LOST");else puts("WIN");}    return 0;}


E - Fliping game

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

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

#define mem(a,x) memset(a,x,sizeof(a))#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<queue>#include<set>#include<stack>#include<cmath>#include<map>#include<stdlib.h>#include<cctype>#include<string>#define Sint(n) scanf("%d",&n)#define Sll(n) scanf("%I64d",&n)#define Schar(n) scanf("%c",&n)#define Sint2(x,y) scanf("%d %d",&x,&y)#define Sll2(x,y) scanf("%I64d %I64d",&x,&y)#define Pint(x) printf("%d",x)#define Pllc(x,c) printf("%I64d%c",x,c)#define Pintc(x,c) printf("%d%c",x,c)using namespace std;typedef long long ll;int main(){    int T;Sint(T);while(T--){int n,m;Sint2(n,m);int s ;for (int i = 0;i < n;++i){for (int j = 0;j < m;++j){Sint(s);}} if (s) puts("Alice");else puts("Bob");}     return 0;}


2.Nim 游戏变形:

U - John

Nim游戏的简单变形,特判全部是1的情况:如果全部是1,奇败偶胜,否则就按Nim游戏的异或和为0的是败态
#define mem(a,x) memset(a,x,sizeof(a))#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<queue>#include<set>#include<stack>#include<cmath>#include<map>#include<stdlib.h>#include<cctype>#include<string>#define Sint(n) scanf("%d",&n)#define Sll(n) scanf("%I64d",&n)#define Schar(n) scanf("%c",&n)#define Sint2(x,y) scanf("%d %d",&x,&y)#define Sll2(x,y) scanf("%I64d %I64d",&x,&y)#define Pint(x) printf("%d",x)#define Pllc(x,c) printf("%I64d%c",x,c)#define Pintc(x,c) printf("%d%c",x,c)using namespace std;typedef long long ll;//const int N = 100;//int a[N];int main(){    int n;    int T;cin>>T;    while (T--)    {    int s = 0;cin>>n;    bool  allone = 1;    for (int i = 1,x;i <= n;++i)    {    Sint(x);    s ^= x;    if (x > 1) allone = 0;}if (allone)//奇败偶胜 {if (n&1) puts("Brother");else puts("John");}else {if (s) puts("John");    else puts("Brother");}}    return 0;}

T - Be the Winner
和上面一题一样的规律,完全不一样的游戏却有完全一样的规律
#define mem(a,x) memset(a,x,sizeof(a))#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<queue>#include<set>#include<stack>#include<cmath>#include<map>#include<stdlib.h>#include<cctype>#include<string>#define Sint(n) scanf("%d",&n)#define Sll(n) scanf("%I64d",&n)#define Schar(n) scanf("%c",&n)#define Sint2(x,y) scanf("%d %d",&x,&y)#define Sll2(x,y) scanf("%I64d %I64d",&x,&y)#define Pint(x) printf("%d",x)#define Pllc(x,c) printf("%I64d%c",x,c)#define Pintc(x,c) printf("%d%c",x,c)using namespace std;typedef long long ll;int main(){int n;while (cin>>n){int s = 0;bool allone = 1;for (int i = 1,x;i <= n;++i){Sint(x);s^=x;if (x > 1) allone = 0;} if (allone)//奇败偶胜{if (n&1) puts("No");else puts("Yes");} else {if (s) puts("Yes");else puts("No");}}        return 0;}
H - Nim or not Nim?
和今年多校里面的一道博弈题基本一样,规律基本都是一样的,这里是可以把石头分两堆,今年多校的那题(HDU 5795)分三堆一样的原理
直接打表找规律,打表的过程注释以供参考
#define mem(a,x) memset(a,x,sizeof(a))#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<queue>#include<set>#include<stack>#include<cmath>#include<map>#include<stdlib.h>#include<cctype>#include<string>#define Sint(n) scanf("%d",&n)#define Sll(n) scanf("%I64d",&n)#define Schar(n) scanf("%c",&n)#define Sint2(x,y) scanf("%d %d",&x,&y)#define Sll2(x,y) scanf("%I64d %I64d",&x,&y)#define Pint(x) printf("%d",x)#define Pllc(x,c) printf("%I64d%c",x,c)#define Pintc(x,c) printf("%d%c",x,c)using namespace std;typedef long long ll;//const int N = 1000;//int sg[N+6];//int SG(int st)//{//if (sg[st]!=-1) return sg[st];//set<int>s;//s.insert(SG(0));//for (int i = 1;i < st;++i)//{//s.insert(SG(st-i));//拿 //s.insert(SG(i)^SG(st-i) );//分 //}//int g = 0;//while (s.count(g)) ++g;//sg[st] = g;//return sg[st];//}//void solve()//{//mem(sg,-1);//sg[0] = 0;//for (int i = 1;i <= 50;++i)//{//cout<<i<<" :"<<SG(i)<<endl;//}//}int SG(int st){if (st == 0) return 0;if (st%4==0) return st-1;if (st%4==3) return st+1;return st; }int main(){//    solve();int T;cin>>T;while (T--){int n;Sint(n);int s = 0;for (int i = 1,x;i <= n;++i){Sint(x);s ^= SG(x);}if (s) puts("Alice");else puts("Bob");}    return 0;}
PS:另附HDU 5795代码对比:(表打出来了规律就很简单了)
#define mem(a,x) memset(a,x,sizeof(a))#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<queue>#include<set>#include<stack>#include<cmath>#include<map>#include<stdlib.h>#include<cctype>#include<string>#define Sint(n) scanf("%d",&n)#define Sll(n) scanf("%I64d",&n)#define Schar(n) scanf("%c",&n)#define Sint2(x,y) scanf("%d %d",&x,&y)#define Sll2(x,y) scanf("%I64d %I64d",&x,&y)#define Pint(x) printf("%d",x)#define Pllc(x,c) printf("%I64d%c",x,c)#define Pintc(x,c) printf("%d%c",x,c)using namespace std;typedef long long ll;//const int N = 1000;//int sg[N+5];//int SG(int st)//{//if (sg[st]!=-1) return sg[st];//set<int>s;//s.insert(SG(0));//for (int i = 1;i < st;++i)//{//s.insert(SG(st-i));//拿//for (int j = 1;j+i < st;++j) //{//s.insert(SG(i)^SG(j)^SG(st-i-j));//分//}//}//int g = 0;//while (s.count(g)) ++g;//sg[st] = g;//return sg[st];//}//void solve()//{//mem(sg,-1);//sg[0] = 0;//for (int i = 1;i <= 50;++i)//{//cout<<i<<": "<<SG(i)<<endl;//}//}int SG(int st){if (st == 0) return 0;if (st%8 == 0) return st-1;if (st%8 == 7) return st+1;return st;}int main(){//    solve();int T;cin>>T;while (T--){int n;Sint(n);int s = 0;for (int i = 1,x;i <= n;++i){Sint(x);s^=SG(x);}if (s) puts("First player wins.");else puts("Second player wins.");}    return 0;}


F - Daizhenyang's Coin

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

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

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

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

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

#define mem(a,x) memset(a,x,sizeof(a))#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<queue>#include<set>#include<stack>#include<cmath>#include<map>#include<stdlib.h>#include<cctype>#include<string>#define Sint(n) scanf("%d",&n)#define Sll(n) scanf("%I64d",&n)#define Schar(n) scanf("%c",&n)#define Sint2(x,y) scanf("%d %d",&x,&y)#define Sll2(x,y) scanf("%I64d %I64d",&x,&y)#define Pint(x) printf("%d",x)#define Pllc(x,c) printf("%I64d%c",x,c)#define Pintc(x,c) printf("%d%c",x,c)using namespace std;typedef long long ll;int getone(int x)//返回二进制1的个数{int t = 0;while (x){if (x&1) ++t;x>>=1;}return t;} int SG(int x){if (getone(x)&1) return 2*x;else return 2*x+1;}ll a[111];int main(){    int n;    while (cin>>n)    {    ll s = 0;    for (int i = 0;i < n;++i)    {    Sll(a[i]);}sort(a,a+n);n = unique(a,a+n)-a;for (int i = 0;i < n;++i){s ^= SG(a[i]); }if (!s) puts("Yes");else puts("No");}    return 0;}


3.状态转移:

Q - Being a Good Boy in Spring Festival
一般博弈都是问当前的局面是胜态还是败态,这个问如果是胜态,第一步有几种走法
真正理解博弈的会明白,博弈双方对局面做出的转移
当某人面对胜态的时候,他会将胜态转移成败态,
而面对败态的人不管怎么操作,只能将局面由败态转为胜态(不包含平局)
这是因为,如果异或和为0(败态)不管怎么操作都将使异或和变为非0(胜态)
而异或和不为0(胜态),一定有策略将异或和变为0(败态)
所以这题就是找,如果面对的是异或和不为0的胜态,有多少种方案将其变成异或和为0的败态
关于异或,有个很有用的性质:a^a^b = b  (即相同的数异或为0),具体操作看代码
#define mem(a,x) memset(a,x,sizeof(a))#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<queue>#include<set>#include<stack>#include<cmath>#include<map>#include<stdlib.h>#include<cctype>#include<string>#define Sint(n) scanf("%d",&n)#define Sll(n) scanf("%I64d",&n)#define Schar(n) scanf("%c",&n)#define Sint2(x,y) scanf("%d %d",&x,&y)#define Sll2(x,y) scanf("%I64d %I64d",&x,&y)#define Pint(x) printf("%d",x)#define Pllc(x,c) printf("%I64d%c",x,c)#define Pintc(x,c) printf("%d%c",x,c)using namespace std;typedef long long ll;/*将非0态(胜态)转化为0态(负态)有多少种方案 */ const int N = 100;int a[N+4]; int s;bool ok(int x){int ns = s^x;//把x变成更小的数,使状态变为0 if (ns < x) return 1;else return 0;}int main(){    int n;while (cin>>n){if (!n) break;s = 0;for (int i = 1;i <= n;++i){Sint(a[i]);s ^= a[i];}if (s == 0) puts("0");else {int sun = 0;for (int i = 1;i <= n;++i){if (ok(a[i])) ++sun;}Pintc(sun,'\n');}}     return 0;}

P - Public Sale
一样的水题打表,不过问的是第一次的选择有哪些,那么枚举第一次的选择,判断子局面是不是败态即可
(也就是只能将败态留给对手)
#define mem(a,x) memset(a,x,sizeof(a))#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<queue>#include<set>#include<stack>#include<cmath>#include<map>#include<stdlib.h>#include<cctype>#include<string>#define Sint(n) scanf("%d",&n)#define Sll(n) scanf("%I64d",&n)#define Schar(n) scanf("%c",&n)#define Sint2(x,y) scanf("%d %d",&x,&y)#define Sll2(x,y) scanf("%I64d %I64d",&x,&y)#define Pint(x) printf("%d",x)#define Pllc(x,c) printf("%I64d%c",x,c)#define Pintc(x,c) printf("%d%c",x,c)using namespace std;typedef long long ll;const int  N= 2111;int sg[N];int n,m;void turn(int n){for (int i = 1;i <= m;++i){sg[n+i] = 1;}}void fool(){mem(sg,0);sg[0] = 0;for (int i = 0;i <= n;++i){if (sg[i] == 0) turn (i);} }bool ok(int x)//将此局面给 对手,对手能否赢 {mem(sg,0);sg[x] = 0;for (int i = x;i <= n;++i){if (sg[i] == 0) turn(i);}if (sg[n] == 0)//对手不能赢return 1;else return 0; }int main(){    while (cin>>n>>m)//n 是成本,m是可以加的数     {      fool();    if (sg[n] == 0) puts("none");    else     {    bool first = 1;    for (int i = 1;i <= m;++i)    {    if (ok(i)) {if (first){printf("%d",i);first = 0;}else printf(" %d",i);}}puts("");}    }    return 0;}


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面):
#define mem(a,x) memset(a,x,sizeof(a))#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<queue>#include<set>#include<stack>#include<cmath>#include<map>#include<stdlib.h>#include<cctype>#include<string>#define Sint(n) scanf("%d",&n)#define Sll(n) scanf("%I64d",&n)#define Schar(n) scanf("%c",&n)#define Sint2(x,y) scanf("%d %d",&x,&y)#define Sll2(x,y) scanf("%I64d %I64d",&x,&y)#define Pint(x) printf("%d",x)#define Pllc(x,c) printf("%I64d%c",x,c)#define Pintc(x,c) printf("%d%c",x,c)using namespace std;typedef long long ll;bool moni(int a,int b){bool win = 1;while(1){if (a>b) swap(a,b);if (b%a == 0) break;if (b-a>a) break;b -= a;win = !win;}return win;}int main(){    int a,b;    while (cin>>a>>b)    {    if (a==0&&b==0) break;    if ( moni(a,b))  puts("Stan wins");    else puts("Ollie wins");}    return 0;}

G - Game
非常神奇,和二分图也联系起来了,想清楚了就是Nim游戏变形
#define mem(a,x) memset(a,x,sizeof(a))#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<queue>#include<set>#include<stack>#include<cmath>#include<map>#include<stdlib.h>#include<cctype>#include<string>#define Sint(n) scanf("%d",&n)#define Sll(n) scanf("%I64d",&n)#define Schar(n) scanf("%c",&n)#define Sint2(x,y) scanf("%d %d",&x,&y)#define Sll2(x,y) scanf("%I64d %I64d",&x,&y)#define Pint(x) printf("%d",x)#define Pllc(x,c) printf("%I64d%c",x,c)#define Pintc(x,c) printf("%d%c",x,c)using namespace std;typedef long long ll;/*1. 分成一个二分图    <span style="white-space:pre"></span>如果可以从A拿卡片到B,连一条从A到B的边。把所有box编号x满足((x%3==0&&x%2==1) || x%3==1)这个条件的放左边,其他放右边,不难发现 a) 只有从左边到右边的边或从右到左的边。 b) 所有不能拿卡片出去的box都在左边。2. 证明左边的box并不影响结果。假设当前从右边的局势来看属于输家的人为了 摆脱这种局面,从左边的某盒子A拿了n张卡片到B,因为B肯定有出去的边,对手 会从B再取走那n张卡片到左边,局面没有变化 3. 于是这就相当于所有右边的box在nim游戏。*/ int main(){    int T;cin>>T;    int kas = 0;while (T--){int n;scanf("%d",&n);int s = 0;for (int i = 1,x;i <= n;++i){scanf("%d",&x);//if ((i%2==1&&i%3==0)) continue;if ((i%3==0&&i%2==0)||i%3==2) s^=x;}printf("Case %d: ",++kas);if (s) puts("Alice");else puts("Bob");}     return 0;}

B - Gems Fight!
局面的描述比较复杂,使用状态压缩博弈,一样的博弈原理,从终态去逆推当前面对的局面
另写了详细题解: HDU 4778 Gems Fight!(博弈+状压)

C - Mine
这个题才真正让人看到SG的作用,前面说当SG和Nim游戏的异或和的结论结合的时候可能并没有什么感觉
这题就很好的应用了这点,整个棋盘的sg就是每个格子的sg的异或和
另写了详细题解: HDU 4678 Mine (博弈SG+自由度原理)

2 0