博弈论基础(acwing)

首先了解几个概念:

NIM游戏:给定N堆物品,第i堆物品有ai个。两名玩家轮流行动,每次可以任选一堆,取走任意多个物品,可以把一堆取完,但不能不取,取走最后一件物品获胜。两人都采取最优策略,问先手是否必胜。

NIM也满足下面两种游戏定义:(这些概念都不重要,理解这个想法就行)

公平组合游戏ICG:

若一个游戏满足:

1.由两名玩家交替行动;

2.在游戏的任意时刻,可以执行的合法行动与轮到那名玩家无关;(所以下棋就不按满足该条件)

3.不能行动的玩家判负;

有向图游戏:

给定一个有向无环图,图中有一个唯一的起点,在起点上放有一枚棋子,两名玩家交替地把这枚棋子延有向边进行移动,每次可以移动一步,不能移动者判负。

NIM游戏就可以把每一个状态视为一个点,从起始状态开始每个人将其移动到下一状态,不能移动的人判负

然后我们理解一件事情

先手必胜:就是在某一情况下,先手存在一种移动后可以使后手面对一种先手必败的状态;

先手必败:就是无论怎样移动,下一个状态一定是先手必胜的状态;

我们的先手必败一定有一个(0,0,0……0)

再然后我们来看一个证明:

是不是发现上面两个结论与先手必胜和先手必败的说法极其类似呢?

并且先手必败的000……0 也对应异或为0的起始条件000……0

于是我们可以认为如果a1^a2^……^an=0,则这是一种先手必败态

如果a1^a2^……^an=x(x!=0),则这是一种先手必胜态

那么我们来看一道基本例题:

 代码:

#include <bits/stdc++.h>

using namespace std;

int main()
{
	int t;
	t=1;
	while (t--)
	{
		int ans;
		int n;
		cin >> n;
		for (int i = 0; i < n; i++)
		{
			int a;
			cin >> a;
			if (i)
			{
				ans ^= a;
			}
			else
			{
				ans = a;
			}
		}
		if (ans)
		{
			cout << "Yes" << endl;
		}
		else
		{
			cout << "No" << endl;
		}
	}
	return 0;
}

 

下一道例题:

 思路:

这既然是一个NIM问题,我们就考虑能不能用异或来做

1.对于偶数上的石子,如果对手移动偶数上的石子,无论多少个,我们只需要,他拿多少个石子放到下一奇数台阶,我们就从这个奇数台阶拿多少个到下一偶数台阶就行,这样就能保证奇数台阶上的石子数不变,直至偶数台阶上全为0;

2.由1可知,我们不需要考虑偶数台阶上的石子,只要能使奇数台阶上的石子数为0,则先手必胜;

诶,那我们就发现,这不就是异或为0吗,由我们上面的结论:如果a1^a3^……^a(2n-1)异或为0,则先手不可能使他全部为0,就是先手必败;如果a1^a3^……^a(2n-1)异或不为0,则先手可能使他全部为0,就是先手必胜。

上代码:

#include <bits/stdc++.h>
using namespace std;

int main()
{
	int n;
	cin >> n;
	int ans = 0;
	for (int i = 1; i <= n; i++)
	{
		int a;
		cin >> a;
		if (i % 2 != 0)
		{
			ans ^=a;
		}
	}
	if (ans == 0)
	{
		cout << "No";
	}
	else
	{
		cout << "Yes";
	}
	return 0;
}

继续哈

我们再看一道题:

 这个和上面两道题不太一样,但也是NIM游戏

要解决这个问题,我们先看几个概念:

Mex运算:设S表示一个非负数集合,定义mex(S)为求出不属于集合S的最小的一个非负整数,(比如mex{1,2,3,4}=0;mex{0,1,3,5}=2)

SG函数:(概念我不写了,举个例子吧)

 这就是sg函数。

sg函数起始节点x的sg(x)=0,说明他不能够到达0这个状态;sg(x)!=0,说明他能够到达0这个状态。这不就满足任何一种非零的状态,是有可能走到0的,满足先手必胜;任何一种为0的状态,是必不可能为0的,满足先手必败

通常博弈时是不止有一个sg函数的,是有多个起始节点,因此我们就可以将所有的sg异或,如果为0,先手必败;不为0,先手必胜。(这里就是把所有的sg视为堆石子,把下一个状态视为拿石子,就跟上面一样,用异或)

对于这道题,我们就可以对每一堆石子遍历能减去的数,得到一个sg函数,求出一堆石子的sg,异或,看为不为0即可;

上代码:

#include <bits/stdc++.h>
#include <unordered_set>//哈希函数的头文件
using namespace std;
const int N = 110, M = 10010;

int f[M], a[N];
int k;
int sg(int n)
{
	if (f[n] != -1)
	{
		return f[n];
	}
	unordered_set<int> S;//建立一个哈希函数用来放sg下一个状态的值,这样能快速查询到一个数有没有
	for (int i = 1; i <= k; i++)
	{
		if (n - a[i] >= 0)
		{
			S.insert(sg(n - a[i]));
		}
	}
	for (int i = 0;; i++)
	{
		if (!S.count(i))
		{
			return f[n] = i;
		}
	}
}

int main()
{
	int n;
	cin >>k;
	for (int i = 1; i <= k; i++)
	{
		cin >> a[i];
	}
	cin >> n;
	int ans = 0;
	memset(f, -1, sizeof(f));//记忆化搜索,不然sg会算很多遍
	for (int i = 1; i <= n; i++)
	{
		int res;
		cin >> res;
		ans ^= sg(res);
	}
	if (ans == 0)
	{
		cout << "No";
	}
	else
	{
		cout << "Yes";
	}
	return 0;
}

 

快结束啦,坚持一下,最后一道:

这道题就是列出每一堆的石子的下一个状态,因为每次可以递减1,所以状态很多

就是sg(100)-->sg(99,99);sg(100)-->sg(98,99)等等等等

然后就是跟上面一样求起始sg就行,只是用到了一个性质:sg(x1,x2)相当于一个局面拆分成了两个局面,由SG函数理论,多个独立局面的SG值,等于这些局面SG值的异或和。

sg(x1,x2)=sg(x1)^sg(x2)

代码!

#include <bits/stdc++.h>
#include <unordered_set>
using namespace std;
const int N = 110;
int f[N];
int sg(int x)
{
	if (f[x] != -1)
	{
		return f[x];
	}
	unordered_set<int> S;
	for (int i = 0; i < x; i++)
	{
		for (int j = 0; j <= i; j++)
		{
			S.insert(sg(i) ^ sg(j));
		}
	}
	for (int i = 0;; i++)
	{
		if (!S.count(i))
		{
			return f[x] = i;
		}
	}
}

int main()
{
	int n;
	cin >> n;
	int ans = 0;
	memset(f, -1, sizeof(f));
	for (int i = 0; i < n; i++)
	{
		int a;
		cin >> a;
		ans ^= sg(a);
	}
	if (ans)
	{
		cout << "Yes";
	}
	else
	{
		cout << "No";
	}
	return 0;
}

OK,结束!

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值