所谓博弈论,就是说在两个人的公平游戏里面预测到结果。公平游戏是说两个人轮流操作,操作次数都是一次,而且可进行的操作也必须是一样的。
公平游戏大概有以下这么几个性质:
- 当其中一方已经无路可走的时候,对于这个人来说这个游戏属于必败态。
- 对于一个人,如果下一个操作是必败的,那么这个人就属于必胜态。
- 对于一个人,如果接下来如何操作都是对方必胜,那这个人必败
我觉得以上这几个性质都是比较显然的。
那么,先来个题暖暖场吧!
硬币游戏1
A l i c e Alice Alice和 B o b Bob Bob在玩这样一个游戏。给定 k k k个数字 a 1 , a 2 , . . . , a k a_1,a_2,...,a_k a1,a2,...,ak。一开始,有 x x x枚硬币, A l i c e Alice Alice和 B o b Bob Bob轮流取硬币。每次所取硬币的数量一定要在 a 1 , a 2 , . . . , a k a_1,a_2,...,a_k a1,a2,...,ak之中。 A l i c e Alice Alice先取,取走最后一枚硬币的一方获胜。当双方都采取最优策略时,谁会获胜?题目假定 a 1 , a 2 , . . . , a k a_1,a_2,...,a_k a1,a2,...,ak中一定有 1 1 1。
限制条件
- 1 < = x < = 10000 1 <= x <= 10000 1<=x<=10000
- 1 < = k < = 100 1 <= k <= 100 1<=k<=100
- 1 < = a i < = x 1 <= a_i <= x 1<=ai<=x
样例输入
x = 9 x = 9 x=9
k = 2 k = 2 k=2
a = { 1 , 4 } a = {1, 4} a={1,4}
样例输出
A l i c e Alice Alice
我们来对这个题进行分析。
情况一:「取完所有硬币就是获胜」,那也就是说,如果轮到里的时候你前面没有硬币了,那很抱歉,你输了。
情况二:既然这样,我们再考虑一下,如果你面前的硬币的数量正好在 a [ ] a[] a[]中,那么你就一下全都拿走,拿下一个人就没有硬币拿了,你不就赢了吗??
情况三:如果轮到你了,你发现无论你怎么取,对方都会出现情况二,那也就说明你寄了。
根据以上三种情况,我们可以想到——对!我们可以用动态规划来解决这个题,上面三个情况就是转移方程(的汉语表达方式)
我们将状态用 b o o l bool bool数组 k [ ] k[] k[]来表示, 1 1 1表示必胜态, 0 0 0表示必败态, [ ] [] []里的数字表示硬币的数量,在下面我们用 i i i来表示。那么:
- 没有硬币就寄,那么就可以设 k [ 0 ] = f a l s e k[0] = false k[0]=false;
- 如果可以将对手逼进必败态,那么就必胜。进行循环,如果 k [ i − a [ k ] ] , 那 么 k [ i ] = 1 k[i - a[k]],那么k[i] = 1 k[i−a[k]],那么k[i]=1(在这里 a [ ] a[] a[]表示 a 1 , a 2 , . . . , a k a_1,a_2,...,a_k a1,a2,...,ak, k k k表示 a [ ] a[] a[]的循环次数)。
那么,代码就非常好写了!
【自制】硬币游戏1
练练手吧 题解会放在最后的
那么,我们再来看一道题吧
A F u n n y G a m e A Funny Game AFunnyGame
【POJ 2484】A Funny Game
这个题 n n n居然到了 1 e 6 1e6 1e6!那么状态就非常多, n 2 n^2 n2的算法肯定也跑不过去。所以说,这个题不能用 D P DP DP
那怎么办??
来分析一下这道题
事实上,是有这么一个规律的:一般只要后手模仿先手的操作,那么后手必胜。大家可以自己试一下,确实是这样的,只要后手对称的进行操作,那么先手是不可能赢的。所以说,除非只有一个或者两个硬币,否则后手必输。
稍微耐心一点,手动模拟一下,你就会觉得原来在这么神奇
代码也懒得留到后面了,直接手写:
#includeusing namespace std; int main(){ int n; cin >> n; if(n <= 2) cout << "Alice" << endl; else cout << "Bob" << endl; return 0; }
那我们再来看一个题好啦
P1290 欧几里德的游戏
欧几里德的两个后代Stan和Ollie正在玩一种数字游戏,这个游戏是他们的祖先欧几里德发明的。给定两个正整数M和N,从Stan开始,从其中较大的一个数,减去较小的数的正整数倍,当然,得到的数不能小于0。然后是Ollie,对刚才得到的数,和M,N中较小的那个数,再进行同样的操作……直到一个人得到了0,他就取得了胜利。下面是他们用(25,7)两个数游戏的过程:
S t a r t : 257 Start:25 7 Start:257
S t a n : 117 Stan:11 7 Stan:117
O l l i e : 47 Ollie:4 7 Ollie:47
S t a n : 43 Stan:4 3 Stan:43
O l l i e : 13 Ollie:1 3 Ollie:13
S t a n : 10 Stan:1 0 Stan:10
S t a n Stan Stan赢得了游戏的胜利。
现在,假设他们完美地操作,谁会取得胜利呢?
输入样例:
2 2 2
25 7 25 7 25 7
2 15 2 15 2 15
输出样例
S t a n w i n s Stan wins Stan wins
O l l i e w i n s Ollie wins Ollie wins
假设大的数是 a a a,小的数是 b b b。
如果 a a a是 b b b的倍数的话,那不就可以先手直接把 a a a变成零了嘛。所以说,如果 a a a是 b b b的倍数,那么先手必胜。
那么,经过一次相减的话,不难想到一共就只有两种情况。一种是相减之后的数字小于 b b b,一种是相减之后的数字大于 b b b。那么我们就分情况进行讨论。
- 首先来看小于 b b b的情况。这种情况说实在的特别简单,就不停的相减进行模拟就可以了;
- 大于 b b b的情况。这种情况看似复杂,其实是很简单的。说白了,就是不能给别人机会,直接将情况二减到情况一。我们来举个栗子。先来举个情况一的例子做铺垫。例如 ( 4 , 7 ) (4,7) (4,7)。经过手动模拟之后会发现这是必胜态。那么它的下一个状态 ( 4 , 3 ) (4,3) (4,3)就一定是必败态。为什么呢?因为必胜一定接着必败,在文章一开始说过。那么再举个栗子。 ( 4 , 19 ) (4,19) (4,19)。假设x是使a- b x < b bx
思路也就出来了。
#includeusing namespace std; long long a, b, T; bool f = false; int main(){ cin >> T; while(T--){ f = false; cin >> a >> b; while(1){ if(a < b) swap(a, b); if(a % b == 0) break; if(a - b > b) break; f = 1 - f; a -= b; } if(!f) cout << "Stan wins" << endl; else cout <<"Ollie wins" << endl; } return 0; }
接下来就是大头了!!
----- N i m Nim Nim游戏
【P2197】nim游戏
甲,乙两个人玩 N i m Nim Nim取石子游戏。
n i m nim nim游戏的规则是这样的:地上有 n n n堆石子(每堆石子数量小于 10000 10000 10000),每人每次可从任意一堆石子里取出任意多枚石子扔掉,可以取完,不能不取。每次只能从一堆里取。最后没石子可取的人就输了。假如甲是先手,且告诉你这 n n n堆石子的数量,他想知道是否存在先手必胜的策略。
输入格式:
- 第一行一个整数 T < = 10 T<=10 T<=10,表示有 T T T组数据
- 接下来每两行是一组数据,第一行一个整数 n n n,表示有 n n n堆石子, n < = 10000 n<=10000 n<=10000;
- 第二行有 n n n个数,表示每一堆石子的数量
输出格式:
- 共 T T T行,如果对于这组数据存在先手必胜策略则输出 " Y e s " "Yes" "Yes",否则输出 " N o " "No" "No",不包含引号,每个单词一行。
输入样例:
2 2 2
2 2 2
1 1 1 1 1 1
2 2 2
1 0 1 0 1 0
输出样例
N o No No
Y e s Yes Yes
这个题,答案就是!!!!
如果每堆石子异或起来不等于零,那么先手必胜,否则先手必败。
为什么??
首先我们假设没有石头了,那这种情况就是必败态。
现在我们来举个栗子。每堆石头的个数我用二进制来表示:
10010 100 1 10010 100 1 10010 100 1
它们异或起来是 10111 10111 10111(大概吧)
我们发现,最高位1是(从右往左数)第五位。而且这些石头里必定有的数量的第五位是 1 1 1(这还是比较好理解的,因为相同为零,不同为一,既然是一,那就肯定说明肯定有的石头在一位上是 1 1 1.把这堆的数和异或之后的数异或起来得到a,然后将那一堆石头取出到只有 a a a^原本石头数量 的数量)
这个例子中也就是 10010 10010 10010 ^ 10111 = 00101 10111 = 00101 10111=00101,然后我们发现这个跟其他石头的数量异或起来居然是零!
那么无论下一个人取出多少,都不可能异或为零了,然后这个人再根据上面所推的结论将异或变成零。也就是,如果异或不为零的话先手必胜!
#includeusing namespace std; int t,n; int main(){ scanf("%d",&t); while(t--){ scanf("%d",&n); int ans=0; for(int i=1; i<=n; ++i){ int shu; scanf("%d",&shu); ans^=shu; } if(!ans) printf("Non"); else printf("Yesn"); } }
这个结论,哪怕背都要背过!因为这跟我们后面要讲的息息相关……
Grundy数可以说这个就是综上所述的内容了。
G r u n d y Grundy Grundy数是个什么东西?
G r u n d y Grundy Grundy就是从这一个点开始,除了走一步就能到的点以外的最小非负整数,也就是所谓的 m e x mex mex操作。
每个点取了 G r u n d y Grundy Grundy之后,就跟 n i m nim nim游戏一样了。何出此言?
- N i m Nim Nim游戏中有x颗石子的点可以转化为有 0 , 1 , . . . , x − 1 0,1,...,x-1 0,1,...,x−1颗石头的点
- 从 G r u n d y Grundy Grundy为x开始的点,可以转移到 G r u n d y Grundy Grundy为 0 , 1 , . . . , x − 1 0,1,...,x-1 0,1,...,x−1的状态
脑子累了??来道题清醒一下!
硬币游戏 2 2 2
A l i c e Alice Alice和 B o b Bob Bob在玩这样一个游戏。给定k个数字 a 1 , a 2 , . . . , a k a_1,a_2,...,a_k a1,a2,...,ak。一开始,有n堆硬币,每一堆有 x i x_i xi枚硬币, A l i c e Alice Alice和 B o b Bob Bob轮流取硬币。每次所取硬币的数量一定要在 a 1 , a 2 , . . . , a k a_1,a_2,...,a_k a1,a2,...,ak之中。Alice先取,取走最后一枚硬币的一方获胜。当双方都采取最优策略时,谁会获胜?题目假定 a 1 , a 2 , . . . , a k a_1,a_2,...,a_k a1,a2,...,ak中一定有 1 1 1。
限制条件
- 1 < = n < = 1000000 1 <= n <= 1000000 1<=n<=1000000
- 1 < = k < = 100 1 <= k <= 100 1<=k<=100
- 1 < = a i , x i < = 10000 1 <= ai,xi <= 10000 1<=ai,xi<=10000
样例输入
n = 3 n = 3 n=3
k = 3 k = 3 k=3
a = { 1 , 3 , 4 } a = {1,3,4} a={1,3,4}
x = { 5 , 6 , 8 } x = {5,6,8} x={5,6,8}
样例输出
B o b Bob Bob
经过了前面讲的,这个,太简单了!求一下 G r u n d y Grundy Grundy然后 x o r xor xor起来就切掉了。
威佐夫博弈
威佐夫博弈大概就是有两堆石子,你可以从第一堆里取任意个,也可以从第二个里取任意个,也可以从两堆里取出同样多的任意个石子。最后面对 ( 0 , 0 ) (0, 0) (0,0)局势的就算输。
我们先打表,发现先手面对这样的局势必输:
( 0 , 0 ) , ( 1 , 2 ) , ( 3 , 5 ) , ( 4 , 7 ) , ( 5 , 9 ) . . . . . . (0, 0), (1, 2), (3, 5), (4, 7), (5, 9)...... (0,0),(1,2),(3,5),(4,7),(5,9)......
有发现规律吗?我们把第一个点看作是『第零个点』。我们发现第一个数实际上就是之前没有出现过的最小自然数,第二个数则是第一个数加上这个点的序号。这也就是我把第一个点设为『第零个点』的原因。
我们将这样的点叫做奇异局势
关于奇异局势,有这么几个性质:
- **它非常少。**如果你的地图无限大的话,那么纵坐标等于1的那一列点就只有一个奇异局势。感性理解一下……
- **任何自然数都包含在一个且仅有一个奇异局势中。**有点类似于那个『充要条件』吧。我们设第 k k k个奇异局势为 ( a , a + k ) (a, a + k) (a,a+k), 第 k − 1 k - 1 k−1个奇异局势为 ( a ′ , a ′ + k − 1 ) (a', a' + k - 1) (a′,a′+k−1)。每个局势中的 a , b a,b a,b肯定不同。而且 a a a一定大于 a ′ − 1 a'-1 a′−1,而且 a a a的条件是『没有出现过的最小自然数』,所以也不可能等于 a ′ + k a' + k a′+k。关键就看 b b b。其实也很简单, a a a一定大于 a ′ a' a′,所以 a + k > a ′ + k a + k > a'+ k a+k>a′+k,也就 a + k > a ′ + k − 1 a + k > a'+ k - 1 a+k>a′+k−1了。
- **任何操作都会把奇异局势变为非奇异局势。**即使是两个数同时减一个数,他们的差值也不会变。但是不同的奇异局势差值是不一样的。所以即使是同时减去相同的数,也会变成非奇异局势。
- **可以通过某些操作让非奇异局势变为奇异局势。**因为任何自然数都包含在一个且仅有一个奇异局势中,所以只需要让另一个数加或减一个数使之变成奇异局势就好了。比如说 ( 5 , 20 ) (5, 20) (5,20)很明显这不是奇异局势。但是我们把 20 20 20变成 9 9 9,它就变成奇异局势了。
那么,怎么判断是不是奇异局势呢?我们可以用 B e t t y Betty Betty定理:
设
n
<
m
n < m
n
(
m
−
n
)
∗
(
(
1
+
5
)
/
2
)
=
=
n
(m - n) * ((1 + sqrt5) / 2) == n
(m−n)∗((1+5
)/2)==n 那么这就是一个奇异局势。面对奇异局势先手必输,否则先手必赢。因为先手无论干什么都会让它变成非奇异局势,然后后手通过某些方式把它变成奇异局势这样无限循环…… 可以考虑一下这一道傻逼题,裸的威佐夫博弈。 代码吧,很好懂(话说我的码风变了很多):我日你不会断句吗#include



