博弈学习 1
看完三位博主的文章再做了几个简单题后的简单总结。
可以看原文,比较详细~~
链接 1: http://blog.csdn.net/logic_nut/article/details/4711489
链接 2: http://blog.sina.com.cn/s/blog_83d1d5c70100y9yd.html
链接 3: http://blog.csdn.net/luomingjun12315/article/details/45479073
几个定义与结论:
(1)组合游戏:
1、有且仅有两个玩家
2、两名玩家交替在 有限的移动集合(比如:取石子游戏,石子是有限的,棋盘中的棋盘大小的有限的)中对游戏进行合法的移动(不能不移动)
3、如果轮到某名选手移动,且这个局面的合法的移动集合为空(也就是说此时无法进行移动),则这名选手负。
(2) Nim游戏:
它是组合游戏(Combinatorial Games)的一种,准确来说,属于“Impartial Combinatorial Games”(以下简称ICG)。
通常的Nim游戏的定义是这样的:有若干堆石子,每堆石子的数量都是有限的,
合法的移动是“选择一堆石子并拿走若干颗(不能不拿)”,如果轮到某个人时所有的石子堆都已经被拿空了,则判负(因为他此刻没有任何合法的移动)。
(3)两个状态
P-Position : P 指的是 Previous ,上一次移动的人有必胜策略,即先手必败
N-Position :N 指的是 Next ,这次移动的人有必胜策略,即先手必胜
1. 无法进行任何移动的局面(也就是terminal position)是P-position;
2. 可以移动到P-position的局面是N-position。
3. 所有移动都导致N-position 的局面是P-position。
(我的理解是:如果 所有可以移动到此局面 的局面都是N-Position ,那么此局面是 P-Position;
如果一个局面的状态是 P-Position, 那么这个局面能够转移到的所有局面都是 N-Position
如有不对望指正^ ^)。
这个可以结合链接 1 原文想一想 ~
一个结论:
(Bouton's Theorem)对于一个Nim游戏的局面(a1,a2,...,an),它是P-position当且仅当a1^a2^...^an=0,其中^表示异或(xor)运算。
证明在链接 1 中~
题目:HDU1846 2147 2149 2188
几个题都是简单的巴什博奕。
巴什博奕(Bash Game):有一堆n个物品,两人轮流从堆中取物品,每次取 x 个 ( 1 ≤ x ≤ m)。最后取光者为胜。
HDU 1846
1) 可以用上述的 P-Position 和 N-Position的定义解,多模拟几次就可以发现规律,然后就可愉快解题了。
例如 n m 分别为 6 2时
即石子有 6 颗,两人轮流取,每次可以可取 1~2 颗 。
那么所有的状态应该是这样的:
有x颗时 0 1 2 3 4 5 6
Position P N N P N N P
发现规律了咩~
2) 也可以这样想 ,复制一段来自链接 3 的推导:
如果 n = m + 1, 一次至多取 m 个,所以无论先取者,取了多少个,一定还剩余 x 个( 1 ≤ x ≤ m)。所以,后取者必胜。
因此我们发现了取胜的秘诀:如果我们把 n 表示为 n = (m + 1) * r + s 。(0 ≤ s < m , r ≥ 0)。
先取者 拿走 s 个, 后取者 拿走 k 个 (1 ≤ k ≤ m),那么先取者 再 拿走 m + 1 - k 个。
结果还剩下 ( m + 1 ) * ( r - 1 ) 个。我们只要始终给对手留下 m + 1 的倍数,那么 先取者 肯定必胜。
现在 我们可以知道,如果 s = 0,那么后取者必胜。 否则 先取者 必胜。
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<string> using namespace std; int main() { int t,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; }
HDU2147
题意:
N*M棋盘,最初棋子在右上角,即(1,M) 每次可以往左、下、左下走一步,最后走到左下角的胜 。
最先开始 把它看做一个类似取石子的游戏,每次可走1~2步的(走到左下算两步= =)
然后 WA 掉了 WA 掉了 掉了 了。。
正解是又照着定义模拟了一遍 ,找出规律,即当 N M 都是奇数的时候先手必败。
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<string> using namespace std; int main() { int n,m; while(scanf("%d%d",&n,&m)!=EOF) { if(n==0 && m==0) break; if((n&1) && (m&1)) printf("What a pity!\n"); else printf("Wonderful!\n"); } return 0; }
HDU2188
与 HDU 1846 同理哟
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<string> using namespace std; int main() { int t,n,m; cin>>t; while(t--){ cin>>n>>m; if(n%(m+1)) cout<<"Grass"<<endl; else cout<<"Rabbit"<<endl; } return 0; }
HDU 2149
按照上面的推导 ,如果我们把 n 表示为 n = (m + 1) * r + s 。(0 ≤ s < m , r ≥ 0)。
那么 s 就是第一次的加价 。如果加价大于本价,也就是说先手可以直接买到了 ,那么出的价就可以是 m~n这一段里的任一。
最先开始自己没想清楚,以为 1~s 都可以,= = 后来想了下,如果不直接加到 S ,那不是给了对方可乘之机 = =
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<string> using namespace std; int main() { int t,n,m; while(scanf("%d%d",&m,&n)!=EOF){ if(m%(n+1)){ int x = m%(n+1); if(n>=m){ cout<<m; for(int i=m+1;i<=n;i++) printf(" %d",i); } else printf("%d",x); } else printf("none"); cout<<endl; } return 0; }