题目:硬币游戏1,Alice和Bob在玩这样一个游戏。给定k个数字a1,a2,···ak。 一开始,有x枚硬币,Alice和Bob轮流取硬币。每次所取硬币的枚数

一定要在a1,a2···,ak当中。Alice先取,取走最后一枚硬币的一方获胜。当双方都采取最有策略时,谁会获胜?假定a1a2···ak中一定有1

限制条件:1<=x<=10000 1<=k<=100 1<=ai<=k

样例:

输入

x=9

k=2

a={1,4}

输出

Alice

样例2

x=10

k=2

a={1,4}

输出

Bob

下面考虑轮到自己的时,还有j枚硬币的情况

1、题目规定取光所有硬币就获胜,这等价于轮到自己时如果没有了硬币就失败了。因此,j=0时是必败态

2、如果对于某个i(1<=i<=k),j-ai是必败态的话,j就是必胜态。(如果当前有j枚硬币,只要取走ai枚,对手就必败->自己必胜)

3、如果对于任意的i(1<=i<=k),j-ai都是必胜态的话,j就是必败态(不论怎么取,对手都必胜->自己必败)

根据上面这些规则,我们利用动态规划算法按照j从小到大的顺序计算必胜态必败态。只要看x是必胜态还是必败态,我们就知道谁会获胜了

像这样,通过考虑各个状态的胜负条件,判断必胜态和必败态,是有胜败的游戏的基础

看代码

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<cmath>
#include<math.h>
#include<algorithm>
#include<set>
#include<queue>
typedef long long ll;
using namespace std;
const ll mod=1e9+7;
#define INF 0x3f3f3f
int main()
{bool win[10005];int x,k;int a[110];cin>>x>>k;for(int i=0;i<k;i++)cin>>a[i];win[0]=false;//0枚硬币必败for(int i=1;i<=x;i++){win[i]=false;//先初始化为为必败态for(int j=0;j<k;j++){win[i]|=a[j]<=i&&!win[i-a[j]];//异或运算,有一个为必败态则为必胜态
        }}if(win[x])cout<<"Alice"<<endl;elsecout<<"Bob"<<endl;return 0;
}

2、A Funny Game

题目:n枚硬币排成一个圈。Alice和Bob轮流从中取一枚或两枚硬币。不过,取两枚时,所取的两枚硬币必须是连续的。硬币取走之后留下空位

,相隔空位的硬币视为不连续。Alice开始先取,取走最后一枚硬币的一方获胜。当双方都采取最优策略时,谁会获胜

0<=n<=1000000

输入

n=1

输出

Alice

输入

n=3

输出

Bob

n高达1000000,考虑到还有将连续部分分裂成几段等的情况,状态数非常的多,搜索和动态规划都难以胜任。需要更加巧妙地判断胜败关系

首先,试想一下如下情况。能够把所有硬币分解成两个完全相同的状态,是必败态还是必胜态呢?

事实上,是必败态。不论自己采取何种策略,对手是要在另一组采取相同的策略,就又回到了分成两个相同的组的状态了

不断循环下去,总会轮到自己没有硬币了。也就是说,因为对手取走了最后一枚硬币而败北

接下来,回到正题。Alice在第一步取走了一枚或者两枚硬币之后,原本成圈的硬币变成了长度为n-1或者n-2的链。这样只要Bob在中间位子

根据链长的奇偶性,取走一枚或者两枚硬币,就可以把所有硬币正好分为两个长度相同的链

这也正如我们前面说的必败态。也就是说Alice必败,Bob必胜,只不过当n<=2时,Alice可以在第一次取光,所以胜利的是Alice。在这类游戏中

做出对称的状态再完全模仿对手的策略常常是有效的

看代码

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<cmath>
#include<math.h>
#include<algorithm>
#include<set>
#include<queue>
typedef long long ll;
using namespace std;
const ll mod=1e9+7;
#define INF 0x3f3f3f
int main()
{int n;cin>>n;if(n<=2)cout<<"Alice"<<endl;elsecout<<"Bob"<<endl;return 0;
}

3、Euclid's Game

让我们看一下这个以辗转相除法为基础的游戏

给定两个整数a,b。Stan和Ollie轮流从较大的数字中减去较小数字的倍数。这里的倍数是指1倍,2倍这样的正整数倍,并且相减后的结果不能

小于零。Stan先手,在自己的回合将其中一个数变为0的一方获胜。当双方都采取最优策略时,谁会获胜?

输入

a=64,b=12

输出

Stan wins

输入

a=15,b=24

输出

Ollie wins

让我们来找找看该问题中必胜态和必败态。首先,如果a>b则交换,假设a<b。另外,如果b已经是a的倍数了则必胜,所以假设b并非a的倍数

此时,a和b的关系按照自由度的观点。可以分为以下两类

b-a<a的情况

b-a>a的情况

对于第一种情况,只能从b中减去a,没有选择的余地。相对的,对于第二种情况,有从b中减去a,减去2a,或者更高的倍数的情况

对于第一种情况,要判断必胜还是必败并不难。因为没有选择的余地,如果b-a之后所得状态是必败态的话,他就是必胜态,如果得到的是必胜态的话,它就是必败态

例如,从(4,7)这个状态出发就完全没有选择的余地,按照

(4,7)->(4,3)->(1,3)的顺序,轮到(1,3)的一方将获胜

所以有必胜->必败->必胜 可见(4,7)是必胜态

接下来,我们来看第二种情况是必胜态还是必败态。假设x是使得b-ax<a的整数,考虑一下,从b中减去a(x-1)的情况。例如对于(4,19)则减去12

此时,接下来的状态成了前边讲过的没有选择的情况,如果改状态是必败的话,则当前状态就是必胜态。

那么,如果减去a(x-1)后的状态是必胜态的话,该如何是好?  此时,从b中减去ax后的状态就是减去a(x-1)后的状态唯一可以转移到的状态,根据假设,减去a(x-1)是必胜态,所以该状态是必败态。因此是必胜态

由此可知,对于第二种情况,总是必胜的。所以,从初始状态开始,最先到达有自由度的第二种状态的一方必胜

看代码

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<cmath>
#include<math.h>
#include<algorithm>
#include<set>
#include<queue>
typedef long long ll;
using namespace std;
const ll mod=1e9+7;
#define INF 0x3f3f3f
int a,b;
void solve(int a,int b)
{bool f=true;while(1){if(a>b)swap(a,b);if(b%a==0)break;if(b-a>a)break;b-=a;f=!f;}if(f)cout<<"Stan wins"<<endl;elsecout<<"Ollie wins"<<endl;
}
int main()
{cin>>a>>b;solve(a,b);return 0;
}

4、Nim游戏

算法树上有点简略,没有看懂,参考资料:https://baike.baidu.com/item/Nim游戏/6737105?fr=aladdin

题目大意:有n堆石子,每堆石子有a[i]个。Alice和Bob轮流从非空的石子中取走至少一颗石子。Alice先取,取光所有石子的一方获胜。当双方都采取最优策略时

,谁会获胜?

限制条件:1<=n<=1000000   1<=ai<=10^9

样例:

输入

n=3

a={1,2,4}

输出:

Alice

让我们来看看这个游戏,该游戏的策略也成为了许多游戏的基础。要判断该游戏的胜负只要用异或运算就好了。有以下结论:

a1^a2^...^an!=0    必胜态

a1^a2^...^an==0  必败态

因此,只要计算异或值就知道谁胜了

分析一下为什么是这样的:

有三种情况:

1、无法进行移动     那么它就是必败态

2、可以移动到必败态        那么它就是必胜态

3、所有的移动都导致必胜态    那么它就是必败态

第一个命题显然,无法进行移动只有一个,就是全0,异或仍然是0。

第二个命题,对于某个局面(a1,a2,...,an),若a1^a2^...^an<>0,一定存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。不妨设a1^a2^...^an=k,则一定存在某个ai,它的二进制表示在k的最高位上是1(否则k的最高位那个1是怎么得到的)。这时ai^k<ai一定成立。则我们可以将ai改变成ai'=ai^k,此时a1^a2^...^ai'^...^an=a1^a2^...^an^k=0。

第三个命题,对于某个局面(a1,a2,...,an),若a1^a2^...^an=0,一定不存在某个合法的移动,将ai改变成ai'后满足a1^a2^...^ai'^...^an=0。因为异或运算满足消去率,由a1^a2^...^an=a1^a2^...^ai'^...^an可以得到ai=ai'。所以将ai改变成ai'不是一个合法的移动。证毕。

看代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<stdio.h>
#include<string.h>
#include<cmath>
#include<math.h>
#include<algorithm>
#include<set>
#include<queue>
typedef long long ll;
using namespace std;
const ll mod=1e9+7;
const int maxn=1e6+10;
const ll maxa=32050;
#define INF 0x3f3f3f3f3f3f
ll n;
ll a[maxn];
void solve()
{ll x=0;for(int i=0;i<n;i++)x^=a[i];if(x!=0)cout<<"Alice"<<endl;//总存在一个变化使得x==0,使得它为必败态,所以它本身是必胜态elsecout<<"Bob"<<endl;
}
int main()
{cin>>n;for(int i=0;i<n;i++)cin>>a[i];solve();return 0;
}

题目大意:排成直线的格子上放有n个棋子。棋子i放在左数第a[i]个格子上。Georgia和Bob轮流选择一个棋子向左移动。每次可以移动一个或者任意多格】

但是不允许反超其他的棋子,也不允许将两个棋子放在同一个格子内。无法进行移动的失败。假设Georgia先移动,当双方都采取最优策略时,谁会获胜?

限制条件:1<=n<=1000 1<=a[i]<=10000

输入

3

1 2 3

输出

Bob

输入

8

1 5 6 7 9 12 14 17

输出

Georgia

思路:如果将棋子两两成对当做整体进行考虑,我们就可以把这个游戏转化为Nim游戏了。先按棋子个数分奇偶情况讨论。

我们可以将每对棋子看作Nim中的一堆石子。石子堆中石子的个数等于两个棋子的间隔、

让我们看一下为什么可以这样转化。考虑其中的某一对石子,将右边的棋子向左移动就相当于从Nim的石子堆中取走石子,另一方面,将左边的石子向左移动,就相当于增加石子。这就与Nim游戏不同了。但是,即便对手增加了石子数量,只要将所加部分减回去就回到原来的状态了。所以这个游戏的胜负和Nim游戏胜负是一样的

看代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<stdio.h>
#include<string.h>
#include<cmath>
#include<math.h>
#include<algorithm>
#include<set>
#include<queue>
#include<map>
typedef long long ll;
using namespace std;
const ll mod=1e9+7;
const int maxn=2e5+10;
const ll maxa=32050;
#define INF 0x3f3f3f3f3f3f
int main()
{int n,x=0;int a[1050];cin>>n;for(int i=0;i<n;i++){cin>>a[i];}if(n%2==1)a[n++]=0;sort(a,a+n);for(int i=1;i<n;i++){x^=(a[i]-a[i-1]-1);}if(x==0)cout<<"Bob"<<endl;elsecout<<"Georgia"<<endl;return 0;
}

5、硬币游戏2

Alice和Bob在玩这样一个游戏。给定k个数字q1,a1···ak。一开始,有n堆硬币,每堆各有xi枚硬币。Alice和Bob轮流选出其中一堆硬币,从中取出硬币。每次所取i硬币的枚数一定要在a1,a2

···ak当中。Alice先取,取光硬币的一方获胜。当双方都采取最优策略时谁会获胜?保证a1,a2···ak一定有个1

限制条件:1<=n<=1000000   1<=k<=100 1<=xi,ai<=10000

输入

n=3

k=3

a={1,3,4}

x={5,6,7}

输出

Alice

这和我们之前介绍的硬币问题1相似,只不过那道题中只有一堆硬币,而本题有n堆。如果依然用动态规划的话,状态数高达O(x1x2···xn)

在此,为了高效的求解该问题,引出Grundy值这一重要概念。利用它,不光这个游戏,其他许多游戏都可以转化成前面所介绍的Nim

让我们再来考虑一下只有一堆硬币的情况,qy硬币枚数x所对应的Grundy值的计算方法如下。

int grundy(x)

{

集合S={}

for(j=1...k)

if(a[j]<=x])  将grundy(x-a[j])加入到S中

return 最小的不属于S的非负整数

}

也就是说这样的Grundy值就是除了自己走任意一步所能到达的状态的Grundy值以外的最小非负整数。这样的Grundy值,和Nim游戏中的一个石子堆类似,有如下性质

Nim中有x颗石子的石子堆,能够转移成有0,1,···x-1颗石子的石子堆

从Grundy值为x的状态出发,能够转移到Grundy值为0,1,x-1的状态

只不过,与Nim不同的是,转移后的Grundy值也有可能增加。不过,对手总能够选取合适的策略回到相同的Grundy值的状态。,所以对胜负没有影响。

了解了一堆硬币Grundy值的计算方法之后,就可以将它看作Nim中的一个石子堆。

下面用的动态规划的方法,复杂度为O(xk)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<stdio.h>
#include<string.h>
#include<cmath>
#include<math.h>
#include<algorithm>
#include<set>
#include<queue>
#include<map>
typedef long long ll;
using namespace std;
const ll mod=1e9+7;
const int maxn=1e6+10;
const int maxk=100+10;
const int maxx=1e4+10;
const ll maxa=43200;
#define INF 0x3f3f3f3f3f3f
int n,k,a[maxn],b[maxk];
int grundy[maxx];
void solve()
{//轮到自己时剩下0枚必败grundy[0]=0;int max_a=*max_element(a,a+n);//求取最大元素for(int i=1;i<=max_a;i++){set<int> s;for(int j=0;j<k;j++){if(b[j]<=i)s.insert(grundy[i-b[j]]);}int g=0;while(s.count(g)!=0)//count用来判断g出现的次数,在这里只有0和1之分g++;grundy[i]=g;}//判断胜负int x=0;for(int i=0;i<n;i++)x^=grundy[a[i]];if(x!=0)cout<<"Alice"<<endl;elsecout<<"Bob"<<endl;
}
int main()
{cin>>n>>k;for(int i=0;i<n;i++)cin>>a[i];for(int i=0;i<k;i++)cin>>b[i];solve();return 0;
}

  

6、两个人在玩如下游戏

准备一张分成w*h的格子的长方形纸张,两人轮流切割纸张。要沿着格子的边界切割,水平或者垂直的将纸张切成两部分。切割了n次之后就得到了n+1张纸,每次选择切得的某一张

再进行切割。首先切出只有一个格子的纸张的一方获胜。当双方都采取最优策略时,先手必胜还是必败

限制条件

2<=w,h<=200

这道题也能用Grundy值来计算。当w*h的纸张分成两张时,假设所分得的纸张的Grundy值分别为g1,g2,则这两张纸对应的状态的Grundy值可以表示为g1^g2

看代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<stdio.h>
#include<string.h>
#include<cmath>
#include<math.h>
#include<algorithm>
#include<set>
#include<queue>
#include<map>
typedef long long ll;
using namespace std;
const ll mod=1e9+7;
const int maxn=1e6+10;
const int maxk=100+10;
const int maxx=1e4+10;
const ll maxa=43200;
#define INF 0x3f3f3f3f3f3f
int a[210][210];
int mem[210][210];
int grundy(int w,int h)
{if(mem[w][h]!=-1)return mem[w][h];set<int> s;for(int i=2;w-i>=2;i++)s.insert(grundy(i,h)^grundy(w-i,h));for(int i=2;h-i>=2;i++)s.insert(grundy(w,i)^grundy(w,h-i));int g=0;while(s.count(g)!=0)g++;return mem[w][h]=g;
}
void solve(int w,int h)
{if(grundy(w,h)!=0)cout<<"WIN"<<endl;elsecout<<"LOSE"<<endl;
}
int main()
{int w,h;memset(mem,-1,sizeof(mem));cin>>w>>h;solve(w,h);return 0;
}

转载于:https://www.cnblogs.com/caijiaming/p/9313671.html

找出游戏的必胜的策略(博弈论的学习)相关推荐

  1. 【博弈论】找出游戏的必胜策略

    下文参考博客 通常的Nim游戏的定义是这样的:有若干堆石子,每堆石子的数量都是有限的,合法的移动是"选择一堆石子并拿走若干颗(不能不拿)",如果轮到某个人时所有的石子堆都已经被拿空 ...

  2. 1823. 找出游戏的获胜者( 约瑟夫环问题 )

    文章目录 Question Ideas 1.Answer( Java ) Code 2.Answer( Java ) Code 3.Answer( Java ) Code Question 1823. ...

  3. 约瑟夫环——递推公式详解(leetcode 1823. 找出游戏的获胜者)

    约瑟夫环--递推公式详解(leetcode 1823. 找出游戏的获胜者) 约瑟夫环问题 约瑟夫环(约瑟夫问题)是一个数学的应用问题:已知 n 个人(以编号1,2,3-n分别表示)围坐在一张圆桌周围. ...

  4. LeetCode 1823. 找出游戏的获胜者

    LeetCode 1823. 找出游戏的获胜者 文章目录 LeetCode 1823. 找出游戏的获胜者 题目描述 一.解题关键词 二.解题报告 1.思路分析 2.时间复杂度 3.代码示例 2.知识点 ...

  5. golang力扣leetcode 1823.找出游戏的获胜者

    1823.找出游戏的获胜者 1823.找出游戏的获胜者 题解 代码 1823.找出游戏的获胜者 1823.找出游戏的获胜者 题解 题目:n个位围成一个环,每次删除第k个人,求最后一个人是谁 思路:约瑟 ...

  6. 力扣每日一题2022-05-04中等题:找出游戏的获胜者

    找出游戏的获胜者 题目描述 思路 数学 C++实现 Java实现 Python实现 题目描述 找出游戏的获胜者 思路 数学 第一轮删掉第k个人,问题就变为n-1个人进行这个游戏.假设知道f(n-1, ...

  7. 0504|1823. 找出游戏的获胜者

    0504|1823. 找出游戏的获胜者 这道题使用队列也可以. 遍历 quene 时,从队尾拿出一个成员,然后再从对头插入.如此循环. 这里使用的是链表: var findTheWinner = fu ...

  8. 约瑟夫环问题- leetcode 1823. 找出游戏的获胜者

    1823. 找出游戏的获胜者 使用链表模拟 class Solution {// 模拟public int findTheWinner(int n, int k) { List<Integer& ...

  9. 找出游戏的获胜者(java)

    问题描述:(约瑟夫环) 样例输入: 代码如下:使用链表模拟法 import java.util.Scanner; public class FindTheWinner {//找出游戏的获胜者//1 & ...

最新文章

  1. mysql一些查询方法记录
  2. java循环练习:水仙花数
  3. java: BigDecimal 调用add后,求和结果没有变化
  4. Keras情感分析(Sentiment Analysis)实战---自然语言处理技术
  5. boost::units模块单位/数量操作和转换的测试程序
  6. php 实现两变量值对换,php两个变量的值交换的方法
  7. 贪心/动态规划 - 买卖股票的最佳时机含手续费
  8. 三种常见单片机时钟电路方案,对比其优缺点
  9. cython php,【整理】Cython返回C/C++ struct类型数据 | 勤奋的小青蛙
  10. 计算机组成原理学习笔记-加法器
  11. [更新ing]sklearn(十六):Nearest Neighbors *
  12. 评高工计算机专业是啥系列,评高级工程师需要职称计算机吗?高级工程师计算机模块是什么意思?...
  13. [ESP32][esp-idf] AP+STA实现无线桥接 中转wifi信号 路由器
  14. 前端获取验证码、手机号登录、注册功能
  15. 计算机组成原理7-主存储器—存储器与CPU的连接
  16. OSChina 周四乱弹 ——金毛如何实现部门自助化管理案例图
  17. C#使用教程:由C++转向C#需要看重地疑难
  18. 【spring】PO,VO,DAO,BO,POJO,Bean之间的区别与解释
  19. 文件大小 和 文件占用空间
  20. 如何自学Android(强烈推荐)

热门文章

  1. 教你如何防止网站被挂马!
  2. 使用springboot集成腾讯云短信服务,解决配置文件读取乱码问题
  3. ASP.NET Core搭建多层网站架构【11-WebApi统一处理返回值、异常】
  4. C#中async/await中的异常处理
  5. Html中解决点击 a 标签刷新的问题,实现点击时不刷新
  6. 【报告分享】2021上半年短视频及电商生态研究报告.pdf(附下载链接)
  7. 吐血整理!内部包含3980大数据、机器学习、推荐系统实战课程,仅分享一次
  8. 【报告分享】2020直播电商分析报告-抖音VS快手.pdf(附下载链接)
  9. makefile:5: *** missing separator. Stop.
  10. 用python写行列式_用Python开发线性代数算法(一) | 手写行列式算法的实现