算法模板(5):数学(5):博弈

博弈论

公平组合游戏ICG

  • 若一个游戏满足:
  1. 由两名玩家交替行动;
  2. 在游戏进程的任意时刻,可以执行的合法行动与轮到哪名玩家无关; .
  3. 不能行动的玩家判负;
  • 则称该游戏为一个公平组合游戏。

  • NIM博弈属于公平组合游戏,但城建的棋类游戏,比如围棋,就不是公平组合游戏。因为围棋交战双方分别只能落黑子和白子,胜负判定也比较复杂,不满足条件2和条件3。

  • 先手必胜状态:先手操作一次后可以使剩下的状态变成必败状态。

  • 先手必败状态:不能做任何一次操作使得剩下的状态成为必败状态。

  • M e x Mex Mex 运算:
    设S表示一个非负整数集合。定义 m e x ( S ) mex(S) mex(S) 为求出不属于集合S的最小非负整数的运算,即: m e x ( S ) = m i n { x , x 属于自然数,且 x 不属于 S } mex(S) = min\{x, x属于自然数,且x不属于S\} mex(S)=min{x,x属于自然数,且x不属于S}

  • S G SG SG 函数:
    在有向图游戏中,对于每个节点 x x x,设从出发共有 k k k 条有向边,分别到达节点 y 1 , y 2 , . . y k y_1, y_2, .. y_k y1,y2,..yk。定义 S G ( x ) SG(x) SG(x) x x x 的后继节点 y 1 , y 2 , . . . , y k y_1, y_2, ... ,y_k y1,y2,...,yk S G SG SG 函数值构成的集合再执行 m e x ( S ) mex(S) mex(S) 运算的结果,即

S G ( x ) = m e x ( { S G ( y 1 ) , S G ( y 2 ) . . . . S G ( y k ) } ) SG(x) = mex(\{SG(y_1), SG(y_2). ... SG(y_k)\}) SG(x)=mex({SG(y1),SG(y2)....SG(yk)})

特别地,整个有向图游戏 G G G S G SG SG 函数值被定义为有向图游戏起点 S S S S G SG SG 函数值,即 S G ( G ) = S G ( S ) SG(G) = SG(S) SG(G)=SG(S)

  • 有向图游戏的和:
    G 1 , G 2 , . . . . G m G_1, G_2, .... G_m G1,G2,....Gm 是m个有向图游戏。定义有向图游戏G,它的行动规则是任选某个有向图游戏 G i G_i Gi,并在 G i G_i Gi 上行动一步。G被称为有向图游戏 G 1 , G 2 , . . . G n G_1, G_2, ... G_n G1,G2,...Gn
    有向图游戏的和的SG函数值等于它包含的各个子游戏SG函数值的异或和,即:
    S G ( G ) = S G ( G 1 ) ⊕ S G ( G 2 ) ⊕ . . . ⊕ S G ( G m ) SG(G) = SG(G1) \oplus SG(G2) \oplus ... \oplus SG(Gm) SG(G)=SG(G1)SG(G2)...SG(Gm).
  • 定理:
    有向图游戏的某个局面必胜,当且仅当该局面对应节点的SG函数值大于0。
    有向图游戏的某个局面必败,当且仅当该局面对应节点的SG函数值等于0。
  • 证明方式和经典 Nim 游戏一模一样。注意从起点S一定可以走到小于 SG(S) 的任意一个数字,这个由 SG的定义就知道。

891. Nim游戏

  • 给定n堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。
  • 若一开始是N堆石子: a 1 , a 2 , a 3 , . . . , a n a_1,a_2,a_3,...,a_n a1,a2,a3,...,an
  • a 1 ⊕ a 2 ⊕ a 3 ⊕ . . . ⊕ a n = 0 a_1 \oplus a_2\oplus a_3\oplus...\oplus a_n=0 a1a2a3...an=0:先手必败。
  • a 1 ⊕ a 2 ⊕ a 3 ⊕ . . . ⊕ a n ! = 0 a_1 \oplus a_2\oplus a_3\oplus...\oplus a_n!=0 a1a2a3...an!=0:先手必胜。
  • 证明:假设 a 1 ⊕ a 2 ⊕ . . . ⊕ a n = x a_1 \oplus a_2 \oplus ... \oplus a_n = x a1a2...an=x,假设 x x x 的二进制表示中,最高的一位1在第k位,那么一定存在一个数 a i a_i ai,其第 k k k 位是1. 那么一定有 a i ⊕ x < a i a_i\oplus x <a_i aix<ai,因此,我们让 a i a_i ai 变成 a i ⊕ x a_i \oplus x aix,就可以让异或和为0. 方法就是从 a i a_i ai 中拿走 a i − ( a i ⊕ x ) a_i - (a_i\oplus x) ai(aix) 个石子 ( a i − ( a i − ( a i ⊕ x ) ) = a i ⊕ x a_i - (a_i - (a_i \oplus x)) = a_i \oplus x ai(ai(aix))=aix).

892. 台阶-Nim游戏

  • 题意:现在,有一个n级台阶的楼梯,每级台阶上都有若干个石子,其中第 i i i 级台阶上有 a i a_i ai 个石子 ( i ≥ 1 ) (i≥1) (i1)。两位玩家轮流操作,每次操作可以从任意一级台阶上拿若干个石子放到下一级台阶中(不能不拿)。已经拿到地面上的石子不能再拿,最后无法进行操作的人视为失败。问如果两人都采用最优策略,先手是否必胜。

  • 奇数级台阶上石子异或起来等于0,先手必败。非零先手必胜。

  • 我们仿照经典 Nim 游戏的思考方式,首先全为0的时候为必败态。如果有一个 a i a_i ai不为0,那么需要移动 i i i 次才能到达0台阶。这就意味着,假设 a 1 ⊕ a 3 ⊕ a 5 . . . = x a_1 \oplus a_3 \oplus a_5 ... = x a1a3a5...=x,找到一个 a i a_i ai,拿走 a i − ( a i ⊕ x ) a_i - (a_i\oplus x) ai(aix) 放到 a i − 1 a_{i-1} ai1 中。

893. 集合-Nim游戏

  • 题意:给定n堆石子以及一个由k个不同正整数构成的数字集合S。现在有两位玩家轮流操作,每次操作可以从任意一堆石子中拿取石子,每次拿取的石子数量必须包含于集合S,最后无法进行操作的人视为失败。问如果两人都采用最优策略,先手是否必胜。

  • 很简单的一个模拟。用记忆化搜索来防止指数级别的深搜复杂度。

#include<iostream>
#include<algorithm>
#include<unordered_set>
#include<cstring>
using namespace std;
const int maxn = 110, maxm = 10010;
int N, M;
int s[maxn], f[maxm];
int sg(int x) {
	if (f[x] != -1) return f[x];
	unordered_set<int> S;
	for (int i = 0; i < M; i++) {
		if (x - s[i] >= 0) S.insert(sg(x - s[i]));
	}
	for (int i = 0; ; i++) {
		if (!S.count(i)) return f[x] = i;
	}
}
int main() {
	scanf("%d", &M);
	for (int i = 0; i < M; i++) scanf("%d", &s[i]);
	scanf("%d", &N);
	memset(f, -1, sizeof f);
	f[0] = 0;
	int res = 0;
	while (N--) {
		int x;
		scanf("%d", &x);
		res ^= sg(x);
	}
	if (res == 0) printf("No\n");
	else printf("Yes\n");
	return 0;
}

894. 拆分-Nim游戏

  • 题意:给定n堆石子,两位玩家轮流操作,每次操作可以取走其中的一堆石子,然后放入两堆规模更小的石子(新堆规模可以为0,且两个新堆的石子总数可以大于取走的那堆石子数),最后无法进行操作的人视为失败。问如果两人都采用最优策略,先手是否必胜。
  • 有一个性质, x x x 可以拆成 ( a 1 , a 2 ) , ( b 1 , b 2 ) . . . (a_1, a_2),(b_1, b_2)... (a1,a2),(b1,b2)...,那么 S G ( x ) = m e x ( { S G ( a 1 , a 2 ) , S G ( b 1 , b 2 ) . . . } ) = m e x ( S G ( a 1 ) ⊕ S G ( a 2 ) , S G ( b 1 ) ⊕ S G ( b 2 ) . . . ) SG(x) = mex(\{SG(a_1, a_2),SG(b_1, b_2)...\}) = mex({SG(a_1)\oplus SG(a_2), SG(b_1)\oplus SG(b_2)...}) SG(x)=mex({SG(a1,a2),SG(b1,b2)...})=mex(SG(a1)SG(a2),SG(b1)SG(b2)...).
#include<cstring>
#include<algorithm>
#include<iostream>
#include<unordered_set>
using namespace std;

const int maxn = 110;
int f[maxn], 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() {
	scanf("%d", &N);
	memset(f, -1, sizeof f);
	
	//f[0] 不初始化似乎也可以
	int res = 0;
	for (int i = 0; i < N; i++) {
		int x;
		scanf("%d", &x);
		res ^= sg(x);
	}
	if (res == 0) printf("No\n");
	else printf("Yes\n");
	return 0;
}

1319. 移棋子游戏

  • 题意:给定一个有 N 个节点的有向无环图,图中某些节点上有棋子,两名玩家交替移动棋子。玩家每一步可将任意一颗棋子沿一条有向边移动到另一个点,无法移动者输掉游戏。对于给定的图和棋子初始位置,双方都会采取最优的行动,询问先手必胜还是先手必败。
  • 每一个棋子都是一个独立的图,因此将所有棋子作为图的起点求其对应的 SG 值,异或起来即可。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<unordered_set>
using namespace std;
const int maxn = 2010, maxm = 6010;
int h[maxn], e[maxm], ne[maxm], idx;
int f[maxn], N, M, K;
void add(int a, int b) {
	e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
int sg(int x) {
	if (f[x] != -1) return f[x];
	unordered_set<int> S;
	for (int i = h[x]; i != -1; i = ne[i]) {
		int u = e[i];
		S.insert(sg(u));
	}
	for (int i = 0;; i++) {
		if (!S.count(i)) return f[x] = i;
	}
}
int main() {
	scanf("%d%d%d", &N, &M, &K);
	memset(f, -1, sizeof f);
	memset(h, -1, sizeof h);
	while (M--) {
		int a, b;
		scanf("%d%d", &a, &b);
		add(a, b);
	}
	int res = 0;
	while (K--) {
		int x;
		scanf("%d", &x);
		res ^= sg(x);
	}
	if (res == 0) printf("lose\n");
	else printf("win\n");
	return 0;
}

1321. 取石子

  • 题意:游戏开始时,有 N 堆石子排成一排,然后他们轮流操作(Alice 先手),每次操作时从下面的规则中任选一个:(1)从某堆石子中取走一个;(2)合并任意两堆石子。不能操作的人输。Alice 想知道,她是否能有必胜策略。

  • 说一下为什么在简单情况下(简单状况即每一堆的石子数 > 1),b是奇数等价于先手必胜:

    • 对于每一种奇数状态:
      • 若堆数 > 1,合并两堆;
      • 若堆数 = 1,取一个石子(此堆的石子必然是奇数,因为b是奇数)
    • 对于每一种偶数状态:
      • 若合并,那么b就变成奇数
      • 若取石子:
        • 若该堆的石子数 > 2,取走后仍是简单状态
        • 若该堆的石子数 = 2,取走一个就是1,那么下一个人为了赢,若此时堆数 > 1, 就会把其中这一个石子和其他堆合并;当然如果此时堆数 = 1的话,拿走就赢了就赢了)。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 60, maxm = 50010;
int f[maxn][maxm];
int N;
int dp(int a, int b) {
	int& v = f[a][b];
	if (v != -1) return v;
	if (a == 0) return v = b % 2;
    //小心这个边界
	if (b == 1) return dp(a + 1, 0);

	//a拿走一个
	if (a && !dp(a - 1, b)) return v = 1;
	//b拿走一个或者b内部合并
	if (b && !dp(a, b - 1)) return v = 1;
	//a内部合并
	if (a >= 2 && !dp(a - 2, b ? b + 3 : b + 2)) return v = 1;
	//a, b合并
	if (a && b && !dp(a - 1, b + 1)) return v = 1;

	return v = 0;
}
int main() {
	int T;
	scanf("%d", &T);
	memset(f, -1, sizeof f);
	while (T--) {
		int N;
		scanf("%d", &N);
		int a = 0, b = 0;
		for (int i = 0; i < N; i++) {
			int x;
			scanf("%d", &x);
			if (x == 1) a++;
			else b += b ? x + 1 : x;
		}
		if (dp(a, b)) printf("YES\n");
		else printf("NO\n");
	}
	return 0;
}

1322. 取石子游戏

  • 题意:有 n 堆石子,将这 n 堆石子摆成一排。游戏由两个人进行,两人轮流操作,每次操作者都可以从最左或最右的一堆中取出若干颗石子,可以将那一堆全部取掉,但不能不取,不能操作的人就输了。对于任意给出的一个初始局面,是否存在先手必胜策略。
  • 浙江2009年省选,挖坑。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值