栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

博弈论基础

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

博弈论基础

所谓博弈论,就是说在两个人的公平游戏里面预测到结果。公平游戏是说两个人轮流操作,操作次数都是一次,而且可进行的操作也必须是一样的。

公平游戏大概有以下这么几个性质:

  • 当其中一方已经无路可走的时候,对于这个人来说这个游戏属于必败态。
  • 对于一个人,如果下一个操作是必败的,那么这个人就属于必胜态。
  • 对于一个人,如果接下来如何操作都是对方必胜,那这个人必败

我觉得以上这几个性质都是比较显然的。

那么,先来个题暖暖场吧!


硬币游戏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来表示。那么:

  1. 没有硬币就寄,那么就可以设 k [ 0 ] = f a l s e k[0] = false k[0]=false;
  2. 如果可以将对手逼进必败态,那么就必胜。进行循环,如果 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

那怎么办??

来分析一下这道题

事实上,是有这么一个规律的:一般只要后手模仿先手的操作,那么后手必胜。大家可以自己试一下,确实是这样的,只要后手对称的进行操作,那么先手是不可能赢的。所以说,除非只有一个或者两个硬币,否则后手必输。

稍微耐心一点,手动模拟一下,你就会觉得原来在这么神奇

代码也懒得留到后面了,直接手写:

#include 
using 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。那么我们就分情况进行讨论。

  1. 首先来看小于 b b b的情况。这种情况说实在的特别简单,就不停的相减进行模拟就可以了;
  2. 大于 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

思路也就出来了。

#include 
using 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,然后我们发现这个跟其他石头的数量异或起来居然是零!

那么无论下一个人取出多少,都不可能异或为零了,然后这个人再根据上面所推的结论将异或变成零。也就是,如果异或不为零的话先手必胜!

#include
using 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 
#include 
#include 
using namespace std;
#define endl "n"


int n, m, x, y, t;
    
int main() {
    cin >> x >> y >> t;
    while(t--) {
        cin >> n >> m;
        if(n > m) swap(n, m);
        int b = floor((m - n) * (1.0 + sqrt(5.0)) / 2.0);
        if(b == n) cout << "Farmer John" << endl;
        else cout << "Bessie" << endl;
    }
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/928951.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号