博弈论和SG函数

emmmmm,是因为在一次训练赛中看到了一道题, 然后就去学了一遍单独发出来把
image
image
在nim博弈的定义和证明上算法进阶讲的还是挺详细的, 上道题
洛谷P5675 [GZOI2017]取石子游戏
根据以上定义, 当Alice取完石子后的异或值不为0, 那么一定是一种必败的情况, 假如所取第一堆的数量为\(a_i\), 而其他的石子的异或值大≥\(a_i\), 那么无论Alice怎么取, 都不会使异或值为0, 我们再看数据范围, 每堆石子的数量最多是200, 不超过\(2^8\), 那么我们可以枚举出第一堆所取哪一堆和其他堆石子的状态,设f[i][j]表示,前第i堆石子的状态是j的方案数,当然, 要求n遍, 因为要枚举每一堆作为第一堆, 这样, 这道题就完美解决了

点击查看代码
#include <bits/stdc++.h>

using namespace std;

const int mod = 1e9 + 7;
int n, ans = 0, a[210], f[210][260];

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	for (int i = 1; i <= n; ++i) {
		memset(f, 0, sizeof(f));
		f[0][0] = 1;
		for (int j = 1; j <= n; ++j) {
			for (int k = 0; k < 256; ++k) {
				if (i == j) f[j][k] = f[j - 1][k];
				else f[j][k] = (f[j - 1][k] + f[j - 1][k ^ a[j]]) % mod;
			}
		}
		for (int j = a[i]; j < 256; ++j) 
			ans = (ans + f[n][j]) % mod;
	}
	cout << ans << endl; 
	return 0;
}

我们继续看另一类博弈论游戏:
image

接下来我们引进SG函数
image

image

所以, 我们如果看到博弈论游戏, 可以先算出终止情况并赋值为0, 然后一步步的求出SG函数, 判断异或值, 注意的是一种情况的SG函数是根据所有子情况来算出的, 并且一定要看出等效的情况, 有时候abc和def其实是一样的, 这样会很节省时间
上训练赛的题

K. Alice and Bob-2
首先全为0的话肯定是必败的情况, 不为0的情况下我们就根据这两个条件暴力的去取数, 当然也必须要记忆化(PS: 在全为0的情况下, 我忘了return SG[x] = 0, 会T, 加上这句话后跑的飞快, 并且每次递归的时候记得排序, 因为是一种等效的情况, 否则会很浪费时间)

点击查看代码
#include <bits/stdc++.h>

using namespace std;

int t, n; 
char ch[50];
map < vector < int > , int > SG; 

inline bool check(vector < int > x) {
	for (int i = 0; i <= 25; ++i) 
		if (x[i]) return false;
	return true;
}

inline int Find(vector < int > x) {
	if (SG.find(x) != SG.end()) return SG[x];
	if (check(x)) return SG[x] = 0;
	vector < int > a;
	for (int i = 0; i <= 25; ++i) {
		if (x[i]) {
			vector < int > vec = x;
			--vec[i];
			sort(vec.begin(), vec.end());
			a.push_back(Find(vec));
		}
	}
	for (int i = 0; i <= 25; ++i) {
		for (int j = 0; j <= 25; ++j) {
			if (i == j) continue;
			if (x[i] && x[j]) {
				vector < int > vec = x;
				--vec[i], --vec[j];
				sort(vec.begin(), vec.end());
				a.push_back(Find(vec));
			}
		} 
	}
	sort(a.begin(), a.end());
	for (int i = 0; ; ++i) {
		bool flag = false;
		for (int j = 0; j < a.size(); ++j) {
			if (i == a[j]) {
				flag = true;
				break;
			} else if (a[j] > i) break;
		} 
		if (!flag) return SG[x] = i;
	} 
}

int main() {
	scanf("%d", &t);
	while (t--) {
		scanf("%d", &n);
		int ans = 0;
		while (n--) {
			vector < int > v;
			for (int i = 0; i <= 25; ++i) v.push_back(0);
			scanf("%s", ch + 1);
			int len = strlen(ch + 1);
			for (int i = 1; i <= len; ++i) ++v[ch[i] - 'a'];
			sort(v.begin(), v.end());
			ans ^= Find(v);
		}
		if (ans > 0) puts("Alice");
		else puts("Bob");
	}
	return 0;
} 

洛谷P7395 弹珠游戏(2021 CoE-I C)
总共只有16个棋子, 考虑状压, 并且我们把斜着翻转过来, 变成这个4*4的矩阵, 枚举每一步, 记搜即可

点击查看代码
#include <bits/stdc++.h>

using namespace std; 

typedef long long ll;
const int mod = 998244353;
const int ans = (1 << 16) - 1;
const int N = (1 << 16) + 10;
const int M = 5e2 + 10;

template < typename T > inline void read(T &x) {
	x = 0; T ff = 1, ch = getchar();
	while (!isdigit(ch)) {
		if (ch == '-') ff = -1;
		ch = getchar();
	} 
	while (isdigit(ch)) {
		x = (x << 1) + (x << 3) + (ch ^ 48);
		ch = getchar();
	}
	x *= ff;
}

int t, SG[N], a[20] = {0, 3, 2, 7, 1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 8, 13, 12};

inline int find(int x) {
//	printf("x = %d\n", x);
	if (SG[x]) return SG[x];
	if (x == ans) return 0;
	vector < int > v;
	// 单个 
	for (int i = 1; i <= 4; ++i) {
		for (int j = 1; j <= 4; ++j) {
			int k = (i - 1) * 4 + j - 1;
			if (x & (1 << k)) {
				int y = x - (1 << k);
				v.push_back(find(y));
			}
		}
	}
	// 横着两个,
	for (int i = 1; i <= 4; ++i) {
		for (int j = 1; j <= 3; ++j) {
			int k = (i - 1) * 4 + j - 1;
			int k2 = k + 1;
			if ((x & (1 << k)) && (x & (1 << k2))) {
				int y = x - (1 << k) - (1 << k2);
				v.push_back(find(y));
			}
		}
	} 
	// 竖着两个
	for (int i = 1; i <= 3; ++i) {
		for (int j = 1; j <= 4; ++j) {
			int k = (i - 1) * 4 + j - 1;
			int k2 = k + 4;
			if ((x & (1 << k)) && (x & (1 << k2))) {
				int y = x - (1 << k) - (1 << k2);
				v.push_back(find(y));
			}
		}
	} 
	
	//右下斜两个
	for (int i = 1; i <= 3; ++i) {
		for (int j = 1; j <= 3; ++j) {
			int k = (i - 1) * 4 + j - 1;
			int k2 = k + 5;
			if ((x & (1 << k)) && (x & (1 << k2))) {
				int y = x - (1 << k) - (1 << k2);
				v.push_back(find(y));
			}
		} 
	} 
	
	// 右上斜两个
	for (int i = 2; i <= 4; ++i) {
		for (int j = 1; j <= 3; ++j) {
			int k = (i - 1) * 4 + j - 1;
			int k2 = k - 3;
			if ((x & (1 << k)) && (x & (1 << k2))) {
//				printf("i = %d j = %d\n", i, j);
				int y = x - (1 << k) - (1 << k2);
				v.push_back(find(y));
			}
		}
	} 
	
	//横着3个
	for (int i = 1; i <= 4; ++i) {
		for (int j = 1; j <= 2; ++j) {
			int k = (i - 1) * 4 + j - 1;
			int k2 = k + 1;
			int k3 = k + 2;
			if ((x & (1 << k)) && (x & (1 << k2)) && (x & (1 << k3))) {
				int y = x - (1 << k) - (1 << k2) - (1 << k3);
				v.push_back(find(y));
			}
		}
	} 
	
	//竖着3个
	for (int i = 1; i <= 2; ++i) {
		for (int j = 1; j <= 4; ++j) {
			int k = (i - 1) * 4 + j - 1;
			int k2 = k + 4;
			int k3 = k + 8;
			if ((x & (1 << k)) && (x & (1 << k2)) && (x & (1 << k3))) {
				int y = x - (1 << k) - (1 << k2) - (1 << k3);
				v.push_back(find(y));
			}
		}
	} 
	
	//右下斜3个
	for (int i = 1; i <= 2; ++i) {
		for (int j = 1; j <= 2; ++j) {
			int k = (i - 1) * 4 + j - 1;
			int k2 = k + 5;
			int k3 = k + 10;
			if ((x & (1 << k)) && (x & (1 << k2)) && (x & (1 << k3))) {
				int y = x - (1 << k) - (1 << k2) - (1 << k3);
				v.push_back(find(y));
			}
		}
	} 
	
	//右上斜3个
	for (int i = 3; i <= 4; ++i) {
		for (int j = 1; j <= 2; ++j) {
			int k = (i - 1) * 4 + j - 1;
			int k2 = k - 3;
			int k3 = k - 6;
			if ((x & (1 << k)) && (x & (1 << k2)) && (x & (1 << k3))) {
				int y = x - (1 << k) - (1 << k2) - (1 << k3);
				v.push_back(find(y));
			}
		}
	} 
	for (int i = 0; ; ++i) {
		bool flag = false;
		for (int j = 0; j < v.size(); ++j) {
			int y = v[j];
			if (y == i) {
				flag = true;
				break;
			}
		}
		if (!flag) return SG[x] = i;
	}
}

int main() {
	read(t); 
	while (t--) {
		int cnt = 0;
		char ch = getchar();
		int i = 1; 
		while (i <= 16) {
			ch = getchar();
			if (ch == '*' || ch == '.') {
				if (ch == '.') cnt |= (1 << a[i]);
				++i;
			}
		}
		if (find(cnt) > 0) puts("Possible.");
		else puts("Impossible."); 
	}
	return 0;
}

posted @ 2021-10-26 23:26  海边微风起  阅读(44)  评论(0编辑  收藏  举报