「博弈论 」学习记录

CF48E Ivan the Fool VS Gorynych the Dragon

Gorynych 有 \(H\) 个头,\(T\)条尾巴,每次 Ivan 可以砍掉 \(0\) 到 \(N\) 个头或者 \(0\) 到 \(M\) 条尾巴,但是每当 Ivan 砍掉 Gorynych 的头或者尾巴时,总有一些新头和新尾巴长出来。

如果 Gorynych 的头和尾巴数量之和超过给定的数字 \(R\) ,则 Gorynych 会战胜 Ivan;如果 Ivan 砍掉了 Gorynych 所有的头和尾巴,则 Ivan 取胜;

Ivan 会按照最优策略进攻,即能赢要求尽量赢,而且回合数最小化;赢不了尽量平,也就是无限进行下去;如果平不了则尽量撑更长时间,也就是回合数最大化。

直接暴力建图。然后按题意模拟。如果能赢用 bfs 找出最短路。如果不能赢就找环。有环就会无限下去平局,否则就找输了的最长步数。

代码不想写(?


CF36D New Game with a Chess Piece

有一个 \(n \times m\) 的棋盘,每一步可以从 \((x,y)\) 移动到 \((x+1,y),(x,y+1),(x+k,y+k)\) ,不能移到棋盘外。

从 \((1,1)\) 开始,不能移动者负,Alice和Bob都按照最优策略移动,问先手是否胜利。

先找规律。不考虑 \(k\) 先填一下胜负。

... ... ... ... 0 1
... ... ... ... 1 0
0 1 0 1 0 1
1 0 1 0 1 0

发现最后都是 \(0\) 和 \(1\) 交替。

考虑有 \(k(k>1)\) 的情况。继续打表。发现此时对于一个 \(n=t(k+1),m>n\) 都一定是先手必胜。

发现对于一个 \((k+1)\times(k+1)\) ,\((1,1)\) 和 \((k+1,k+1)\) 的胜率是相反的。所以先手必胜。

而对于一个 \(2(k+1) \times 2(k+1)\) ,先手也是必胜的。

所以可以发现,设 \(x=\min(n,m)\) ,若 \(x \equiv 0 \pmod{(k+1)}\) 则一定是先手必胜。

否则,设 \(p=\left\lfloor \dfrac{x}{k+1} \right\rfloor\) 。打表发现此时对于取膜后的矩阵范围都是 \(0\) 和 \(1\) 交替。如果 \(p\) 和 \((n+m)\) 的奇偶性相同,则先手败,反之先手胜。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int T,n,m,k;
int main()
{	freopen("input.txt","r",stdin);
	freopen("output.txt","w",stdout);
	scanf("%d%d",&T,&k);
	while(T--)
	{	scanf("%d%d",&n,&m);
		if(n<m)swap(n,m);
		if(k==1)
		{	if((n&1)&&(m&1))printf("-\n");
			else printf("+\n");
			continue;
		}
		if(m%(k+1)==0)printf("+\n");
		else{
			int p=m/(k+1);
			if((p&1)==((n+m)&1))printf("-\n");
			else printf("+\n");
		}
	}
	return 0;
}

CF39E What Has Dirichlet Got to Do with That?

Masha和Stas在玩游戏。初始有两个数 \(a,b\),令 \(f(a,b)\) 表示把 \(b\) 个不同的物品放入 \(a\) 个不同的箱子的方案数,可以允许有箱子啥都不放。

Masha和Stas每次可以增加一个箱子或一个物品,但是要求 \(f(a,b)\) 小于给定的 \(k\) ,无法操作者负。Masha先手,两边都按照最优策略走,问谁能赢得胜利 。

其中 \(a \le 10^4,b \le 30,k \le 10^9\) 。

首先,\(f(a,b)=a^b\) 。

因为 \(k \le 10^9\) ,所以 \(f(a,b)\) 的状态数太多,没办法暴力。

但是若先去掉 \(a=1,b=1\) 的情况,情况就少了很多。\(a\) 的上限变成 \(\sqrt{k}\) ,而 \(b\) 变成 \(\log{k}\) 。

所以直接对于所有 \(a>1,b>1\) 暴力预处理,记为 \(g(a,b)\) 。再考虑 \(a=1,b=1\) 。

首先是 \(a=1,b>1\) 。如果此时 \(f(a+1,b)>k\) 则此时只会不停的增加 \(b\) ,也就是说游戏会无限进行下去。

但在平局前可能存在一段是 \(f(a+1,b)<k\) 。也就是说,如果此时的 \(g(2,b)\) 是后手必胜,则此时的先手一定会选择 \(a+1\) 。

当是 \(a>1,b=1\) 时。同样的,如果此时 \(f(a,b+1)=a^2>k\) 即 \(a>\sqrt{k}\) 时只会不停增加 \(a\) 。此时就变成了当前先手和 \(n-a-1\) 的奇偶性讨论。

在这过程前,如果出现一个状态 \(g(a,2)\) 是后手必胜,则此时的先手选择 \(b+1\) 而获胜。

当时 \(a=1,b=1\) 时。分别讨论 \(f(1,2)\) 和 \(f(2,1)\) 的情况即可。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=35000+5,M=31;
int n,a,b,dp[N][M];
bool check(int x,int y)
{	ll res=1ll;
	for(int i=1;i<=y;i++)
	{	res=res*x;
		if(res<=0||res>=n)return 1;
	}
	return 0;
}
int dfs(int x,int y)
{	if(dp[x][y]!=-1)return dp[x][y];
	if(check(x,y))return dp[x][y]=1;
	int res=0;
	res|=!dfs(x+1,y),res|=!dfs(x,y+1);
	return dp[x][y]=res;
}
int checka(int y)
{	if(check(1,y))return 0;
	int sg=0;
	while(!check(2,y))
	{	if(dp[2][y]==0)return (sg==0)?(1):(-1);
		sg^=1;y++;
	}
	return 0;
}
int checkb(int x)
{	int lim=sqrt(n-0.000001);
	int sg=0;
	while(x<=lim)
	{	if(dp[x][2]==0)return (sg==0);
		x++;sg^=1;
	}
	sg=(sg+n-1-x)%2;
	return sg;
}
int main()
{	scanf("%d%d%d",&a,&b,&n);
	if(check(a,b)){printf("Masha\n");return 0;}
	memset(dp,-1,sizeof(dp));
	for(int i=2;i<=N-5;i++)
		for(int j=2;j<M;j++)
			dp[i][j]=dfs(i,j);
	if(a!=1&&b!=1)
	{	if(dp[a][b])printf("Masha\n");
		else printf("Stas\n");
		return 0;
	}
	if(a==1&&b==1)
	{	int x=checka(b+1),y=checkb(a+1);
		if(x<0||y==0)printf("Masha\n");
		else if(x==0)printf("Missing\n");
		else printf("Stas\n");
		return 0;
	}
	if(b==1)
	{	if(checkb(a))printf("Masha\n");
		else printf("Stas\n");
	}
	else if(a==1)
	{	int x=checka(b);
		if(x==0)printf("Missing\n");
		else if(x==1)printf("Masha\n");
		else printf("Stas\n");
	}
	return 0;
}

CF768E Game of Stones

Sam 和 Jon 在玩 Nim 游戏,但是他们厌倦了 Nim 游戏,于是换了一个规则。

每一次一个人在一堆石子中选择某个数目的石子拿走后,另一个人就不能在同一堆石子中选择同样数目的石子拿走。其他规则不变,两边都按最优策略走,问谁能赢。

其中 \(n \le 10^6,x \le 60\) 。

首先先打个表,可以发现 \(1,1,2,2,2,3,3,3,3,4,4,4,4,4,...\) 。也就是说有 \(i+1\) 个 \(i\) 。

也就是 \(sg(x)=\max\{ a|1+2+3+..+a \le x \}\)

来尝试理解一下为什么。

因为每堆石子最多选 \(a\) 次,也就是相当于一个大小为 \(a\) 的普通 Nim 游戏。

#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
using namespace std;
int n,a,sg;
int main()
{	scanf("%d",&n);
	for(int i=1,j;i<=n;i++)
	{	scanf("%d",&a);
		for(j=1;;j++)
		{	a-=j;
			if(a<=j)break;
		}
		sg^=j;
	}
	if(sg)printf("NO\n");
	else printf("YES\n");
	return 0;
}

CF794E Choosing Carrot

A 和 B 得到了 \(n\) 个胡萝卜,需要最终选出一个。每个胡萝卜有特征值 \(a_i\),A 认为 \(a_i\) 大的胡萝卜好,而 B 认为 \(a_i\) 小的胡萝卜好。

他们每次都会在当前序列中最左端或最右端拿掉一个胡萝卜, 最后剩下的一个就是选出的胡萝卜。

A 先手,两个人都足够聪明( A 最大化 \(a_i\) 而 B 最小化 \(a_i\) ),A 为了使自己胜算更大,会在游戏开始之前趁 B 不注意先按照之前的规则移动 \(k\) 步。

对于所有 \(0 \le k \le n-1\),求出最后剩下的胡萝卜的 \(a_i\) 值大小.

其中 \(n \le 3 \times 10^5,a_i \le 10^9\) 。

对于 \(n\) 是偶数的情况,最终答案一定在中间两者之一。因为目的相反,一个人做出决策一定是对另一个人不利的。

所以第二个人会做出相反的决策。最后答案就在中间了。

如果是奇数,则答案是 \(\max(\min(a_{i-1},a_i),\min(a_i,a_{i+1}))\) 。

所以对于每个位置的奇数偶数情况的答案即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int N=3e5+5;
int n,a[N],h[N],g[N],ans[N];
int main()
{	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{	scanf("%d",&a[i]);
		ans[1]=max(ans[1],a[i]);
	}
	for(int i=1;i<n;i++)
		h[min(i,n-i)]=max(h[min(i,n-i)],max(a[i],a[i+1]));
	for(int i=2;i<n;i++)
		g[min(i-1,n-i)]=max(g[min(i-1,n-i)],max(min(a[i],a[i-1]),min(a[i],a[i+1])));
	for(int i=n/2;i>=1;i--)
	{	ans[i<<1]=max(ans[(i+1)<<1],h[i]);
		ans[i<<1|1]=max(ans[(i+1)<<1|1],g[i]);
	}
	for(int i=n;i>=1;i--)
		printf("%d ",ans[i]);
	printf("\n");
	return 0;
}

CF98E Help Shrek and Donkey

有一副互不相同的牌共 \(n+m+1\) 张。有两个人,第一个人 \(n\) 张,第二个人 \(m\) 张。另外有一张牌放在桌子上。

两个人玩游戏轮流操作(其中第一个人为先手)。有如下 \(2\) 中操作类型:

  1. 猜测:猜桌上的那张牌是什么。如果猜对了则获胜,猜错了则失败。操作完之后游戏结束。
  2. 指定:报一张牌的名字,如果对方手上有这张牌,则将该牌丢弃,游戏继续;如果对方手上没有这张牌,对方则会表示他不用由此牌。

现在假设两个人都知道这 \(n+m+1\) 张牌分别是什么,但是不知道桌上和对方手里的牌具体是什么。

若双方都采取最优策略进行游戏,问先手和后手获胜概率。

其中 \(n,m \le 10^3\) 。

首先先手可以选择如实问对方没有的(1),问对方自己有的。或者选择欺骗(2)。

后手可以选择相信先手(1),或对方认为先手在骗他(2)

分类。记 \(f(n,m)\) 表示先手有 \(n\) 张后手有 \(m\) 张的概率。

对于 \((2,1)\) 的情况,先手直接获胜,概率是 \(1\) 。

对于 \((2,2)\) 的情况,后手判断出这张牌是在先手手里的,所以概率是 \(1-f(m,n-1)\) 。

对于 \((1,1)\) 的情况,如果此时先手没猜到了桌上的牌才继续游戏。先手获胜的概率为 \(\dfrac{m}{m+1} \times (1-f(m-1,n))\) 。

对于 \((1,2)\) 的情况,先手获胜的概率是 \(\dfrac{1}{m+1}+\dfrac{m}{m+1}\times (1-f(m-1,n))\) 。

设先手有 \(p\) 的概率走 \((1)\) ,\((1-p)\) 的概率选择 \((2)\) 。此时就可以算出后手选 \((1)\) 或者 \((2)\) 的胜率。

我们可以通过调整 \(p\) 来使自己的胜率最大。

纳什均衡:每个玩家选择一个策略,当一个玩家不改变策略时,没有玩家能从改变策略中获益。

可以得出 \(p \cdot w(1,1)+(1-p) \cdot w(2,1)=p \cdot w(1,2)+(1-p) \cdot w(2,2)\) 。

解得 \(p=\dfrac{w(2,2)-w(2,1)}{w(1,1)-w(2,1)-w(1,2)+w(2,2)}\) 。于是转移变为 \(f(n,m)=w(1,1) \cdot p+(1-p)\cdot w(2,1)\) 。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int N=1000+5;
int m,n;double f[N][N];
double dp(int x,int y)
{	if(f[x][y])return f[x][y];
	if(!y)return f[x][y]=1.0;
	if(!x)return f[x][y]=1.0/(y+1);
	double w11=1.0*y/(y+1)*(1.0-dp(y-1,x)),w21=1.0,w12=w11+1.0/(y+1),w22=1.0-dp(y,x-1);
	double res=(w21-w22)/(w12+w21-w11-w22);
	return f[x][y]=res*w11+(1.0-res)*w21;
}
int main()
{	scanf("%d%d",&m,&n);
	double res=dp(m,n);
	printf("%.10lf %.10lf\n",res,1.000-res);
	return 0;
}
上一篇:力扣Day27


下一篇:iOS 流式布局 UI 框架 CocoaUI 开源