可能是自己做的题目不够多把,自己目前对博弈题的认识是:只要是符合nim规则的博弈,都可以用sg函数求解,但是如果我们只考虑单个游戏,不考虑游戏之间的组合,只考虑必败态和必胜态就好了,自己能力有限,目前对这个结论只有一个感性上的认识,因为自己对sg函数的认识只停留在mex操作这个低级的层面,虽然维基百科上有sg相关的证明,但是自己英语的阅读能力太差,又忙于训练,没有太多的时间。希望大家原谅我不能严谨地证明这个结论,也希望大家能对我提出各种意见。
组合游戏
组合游戏必须使用sg函数,因为各个独立的游戏之间的必败态和必胜态之间已经失去了关联性,无法单纯地用P,N这两个简单的符号表示,而是要利用sg函数以及各个独立游戏sg值的异或和来深层的挖掘目前状态的性质。网上有很多sg函数相关的入门文章,但我觉得这些文章大多都是点到即止,如果你想深入地了解sg函数的相关原理,应该选择直接阅读相关论文,如果你只是想可以成功地切几个题,恕我愚昧的言论:只要记结论就够了。(我不是不鼓励大家去了解背后的原理,我只是想说,在我个人的理解中,如果精力有限,sg函数是无法很好理解的,浅尝辄止的话,反而更不好)
在一些大数据的题目里,sg函数的计算会直接导致TLE,这时就要通过本地打一些小数据的表,来试图找到题目里的规律,当然,有些题目我们可以通过自己分析来得到解题需要的规律或者结论,是通过分析还是通过打表得出,就是仁者见仁智者见智了。
丢一道昨天做到的利用sg函数的博弈题(只是昨天做到而已,没有代表性,但依然可以做做熟悉下sg函数):http://acm.tju.edu.cn/toj/showp4172.html
自己的代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define MS(x, y) memset(x, y, sizeof(x))
const int MAXN = 1e3 + 5;
int n;
int sg[MAXN];
bool sgVis[MAXN];
char str[MAXN];
int main() {
for (int i = 2; i < MAXN; ++i) {
for (int j = 0; j <= i - 2; ++j) {
sgVis[sg[j] ^ sg[i - 2 - j]] = true;
}
for (int j = 0; j < MAXN; ++j) if (!sgVis[j]) {
sg[i] = j;
break;
}
for (int j = 0; j <= i - 2; ++j) {
sgVis[sg[j] ^ sg[i - 2 - j]] = false;
}
// cout << i << ": " << sg[i] << endl;
}
int T;
scanf("%d", &T);
while (T--) {
scanf("%d%s", &n, str);
int cnt = 0, ans = 0;
for (int i = 0; str[i]; ++i) {
if (str[i] == '0') {
ans ^= sg[cnt];
cnt = 0;
} else ++cnt;
}
ans ^= sg[cnt];
puts(ans ? "Alice" : "Bob");
}
}
非组合游戏
其实就是只有一个游戏的意思了,这种题我们可以不依靠sg函数,直接通过必败点和必胜点的判断来找到解题的方法。这样少了mex操作,可以节省时间,自己思考的时候因为只有P和N两种状态,思考起来比sg值更直观。
丢一道最近做过的题:http://acmoj.shu.edu.cn/problem/418/,这题直接通过N点P点打表就好了,即:必败点的前驱一定是必胜点,不需要考虑sg函数。
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXN = 500 + 5;
int n, m;
bool notprime[MAXN];
int prime[MAXN], pnum;
bool win[MAXN][MAXN];
int main() {
for (int i = 2; i < MAXN; ++i) if (!notprime[i]) {
prime[pnum++] = i;
for (int j = i * 2; j < MAXN; j += i) notprime[j] = true;
}
for (int i = 1; i < MAXN; ++i) for (int j = 1; j < MAXN; ++j) if (!win[i][j]) {
for (int k = 0; k < pnum; ++k) {
if (i + prime[k] < MAXN) win[i + prime[k]][j] = true;
if (j + prime[k] < MAXN) win[i][j + prime[k]] = true;
if (i + prime[k] < MAXN && j + prime[k] < MAXN) win[i + prime[k]][j + prime[k]] = true;
}
}
int T;
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m);
if (win[n][m]) puts("Sora");
else puts("Shiro");
}
}