WEEK_11(组合博弈)

本文介绍了博弈理论中的巴什博弈、尼姆博奕和威佐夫博奕,讨论了必败点和必胜点的概念,以及如何通过编程实现这些博弈规则。还涉及了SG函数、拓扑排序在解决博弈问题中的应用,如P1288取数游戏和Nim游戏的代码示例。
摘要由CSDN通过智能技术生成

 在题目之前,还是要一如既往地复习一下下这周学的算法--组合博弈

说到博弈,众所周知的有三种博弈:巴什博奕(Bash Game)、尼姆博奕(Nimm Game)、威佐夫博奕(Wythoff Game)

我们引入两个重要的点:

P点--必败点

N点--必胜点

首先

巴什博弈:

给了一堆石子,数目为n个,现在在这么多石子中取石子,每次取石子数量不超过m个,最终将物品拿完者获胜。那么这个问题背分成以下几种情况:

Case1:n<m+1,则先手胜

Case2:n=m+1,则后手胜

Case3:n=k(m+1)+r,(0<=r<=m)

               a.r=0,n=k(m+1),后手必胜

               b.r!=0,n=k(m+1)+r,先手必胜

抽象不,还是看代码吧

if(n%(m+1)==0)
cout<<"xianshou"<<endl;
else
cout<<"houshou"<<endl;

 如果用P点和N点来说的话,是这样的:

可是如果,m的值不是连续的,而是间断的该咋整呢?

 那么这是引入几条胜败定律:

1、所有终结点都是P点(必败点)

2、以任何N点(必胜点)操作,至少存在有一种方法能进入P点

3、无论如何操作,从P点都能走到的点是N点

 对于这个问题,还可以把它倒着看,从终结点开始,然后往前推导,真不戳。。

然后,比较相似的还有走迷宫(从一点只能往下走一格,往左走一格,或者走到左下角的格),判断先后手赢面-------------->转化一下:两堆牌,抽走某一堆中的一张,或者两堆各一张,抽完者胜。这很好想,就是从0|0开始,往上遍历就完事儿了。


尼姆博奕:

有n堆石子,俩人轮流从石子堆中的一个取任意数量的石子,至少一个,最后拿光石子的人胜利

n=1: 先手必胜。

n=2:俩情况:一种两堆石子相同,另一种一堆比另一堆少

        (m,m) ,先手必输。

        (m,M),先手先从多的一堆中拿出(M-m)个,先手必胜。

(正经人称(m,m)的局面为奇异局势)

n=3:(m,m,M),先手必胜(先手可以先拿M,变成(m,m,0))

 那么,如何用编程来实现呢?

我们再引入Nim-Sum运算:

Nim-Sum运算:

假设(x_{m},......,x_{0}_{2}和(y_{m},......,y_{0}_{2}的nim-sum是(z_{m},......,z_{0}_{2},则有:

x_{m},......,x_{0}_{2} ^(y_{m},......,y_{0}_{2} = (z_{m},......,z_{0}_{2}

z_{k} = x_{k} + y_{k}(mod 2))

人话就是异或运算

 代码:

for(int i=1;i<=n;i++)
{
	cin>>a[i];
	s^=a[i];
}

 如果最后算得s=0,那么必输,反之如果s!=0,那么有必胜的策略


威佐夫博弈:

两堆若干个物品,两个人轮流从任一堆取至少一个或同时从两堆中取同样多的物品,最后取光者胜

 如何应对?

两个人如果都采用正确操作,那么面对非奇异局势,先手必胜;反之,后手必胜

很难想到这题居然和黄金分割比有关:

1.618=(sqrt(5.0)+1)/2
ans=(int)((b-a)*((sqrt(5.0)+1)/2));

 若ans与两堆物品较小值相等,那么后手胜,反之先手胜 


博弈介绍到这,现在我们来看看万能大法--SG函数(Graph Games&Sqrague--Grundy Function)

SG函数

经典问题:尼姆博奕(above)

sg(x)=min\left \{ n\geqslant 0:n<>sg(y) for y \in f(x)) \right \}

x节点的SG值是除去x的后继节点的SG值后的最小非负整数

再有关键的判断:

sg(x)=0;——必败

sg(x)!=0;——必胜

众所周知,博弈问题很多可以当做图问题来做,因此SG函数的用途非常广泛

那么现在我们就有底气解决大多博弈问题了,当然,还有可能有将数个问题组合起来的问题,那么就再来一个定理:

若图游戏G由若干子图游戏Gi组成,即:G=G1+G2+...+Gn,假设gi是Gi的SG函数值,那么图游戏G的SG函数值的计算:g(x_{1},x_{2},...,x_{n})=g(x_{1})\oplus ...g(x_{n})

最后,如果异或和为0,那么当前的合并状态是必败的;

           如果异或和非0,那么当前状态是必胜的。


P1288 取数游戏 II

这题其实不难,我们先按照最效率的方式来走,能发现,直接一次走到对面和来回走几次再过去情况一样,那么就一直直接走,容易得出:最后先手的输赢和非0路径的奇偶有关

代码:

#include<bits/stdc++.h>
using namespace std;
int n;
const int N=35;
int a;
int tot;
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a;
		if(a!=0)
		tot++;
	}
	if(tot%2==0)
	cout<<"NO"<<endl;
	else
	cout<<"YES"<<endl;
}

P2197 【模板】Nim 游戏

略略略~~~~~~~~~~~~~~~

#include<bits/stdc++.h>
using namespace std;
int t;
int n;
const int N=1e4+5; 
int stone[N];
int main()
{
	cin>>t;
	while(t--)
	{
		int s=0;
		cin>>n;
		for(int i=1;i<=n;i++)
		{
			cin>>stone[i];
			s^=stone[i];
		}
		if(s==0)
		cout<<"No"<<endl;
		else
		cout<<"Yes"<<endl;
	}
}

P1113 杂务

拓扑排序:

首先,我们明确一下:有向无环图一定是拓扑序列(一定不能有环!!!)

无向图没有拓扑序列

对于拓扑序列,我们需要引入度——进入某点的路径数为入度,某点指向其他点的路径数为出度,总结一下,拓扑序列中只有从前指向后的边,没有从后指向前的边

对于一个有向无环图,一定有一个点的入度为0,如果找不到一个入度为0的点,这个图一定是带环的

对于拓扑排序,思路如下:

1、记录各个点的入度

2、将入度为 0 的点放入队列

3、找出所有这个点发出的边,删除边,同时边的另一侧的点的入度 -1。

4、如果所有点都进过队列,则可以拓扑排序,输出所有顶点。否则不可以进行拓扑排序。


本题:

首先来存个图,然后把所有入度为0的点放入队列中,再依次遍历,寻找这个点连接的其他点,并将连通关系抹除,同时下一点入度-1,同时dp一下,来一个状态转移方程:

f[i]=max(f[i],f[t]+minute[i]);

表示完成i点任务后的耗时,为了保证其他路径到达i点时任务能够完成,故取max

代码:

#include<bits/stdc++.h>
using namespace std;
int n;
const int N=1e4+5;
int u,v;
int minute[N];//u为起点,v为前驱点,minute为耗时 
int ind[N];
int f[N];
bool connect[N][N];
queue<int> q;
int ans;
void bfs()
{
	int t;
	for(int i=1;i<=n;i++)
	if(!ind[i])
	q.push(i);
	while(q.size())
	{
		t=q.front();
		q.pop();
		for(int i=1;i<=n;i++)
		if(connect[t][i])
		{
			f[i]=max(f[i],f[t]+minute[i]);
			connect[t][i]=connect[i][t]=false;
			ind[i]--;
			if(!ind[i])
			q.push(i);
		}
	}
	for(int i=1;i<=n;i++)
	ans=max(ans,f[i]);
	cout<<ans;
}
int main()
{
	cin>>n;
	memset(connect,false,sizeof(connect));
	for(int i=1;i<=n;i++)
	{
		cin>>u>>minute[i];
		while(1)
		{
			cin>>v;
			if(v==0)
			break;
			ind[u]++;
			connect[u][v]=connect[v][u]=true;
		}
	}
	ind[1]=0;
	f[1]=minute[1];
	bfs();
}

P1002 [NOIP2002 普及组] 过河卒

关于dp乱入博弈。。。

以每个位置累计路径数,若能走就加上前一次走过的点位的dp值,如果不能走,就跳过啦

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int fx[]={0,-2,-1,1,2,2,1,-1,-2};
const int fy[]={0,1,2,2,1,-1,-2,-2,-1};
int bx,by,mx,my;
ll f[40][40];
bool s[40][40];
int main()
{
    cin>>bx>>by>>mx>>my;
    bx+=2;by+=2;mx+=2;my+=2;
    //防越界
    f[2][1]=1;
    s[mx][my]=1;//标记马的位置
    for(int i=1;i<=8;i++)
	s[mx+fx[i]][my+fy[i]]=1;
    for(int i=2;i<=bx;i++)
	{
        for(int j=2;j<=by;j++)
		{
            if(s[i][j])
			continue;//被马拦住就直接跳过
            f[i][j]=f[i-1][j]+f[i][j-1];
        }
    }
    cout<<f[bx][by];
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值