可能是自己做的題目不夠多把,自己目前對博弈題的認識是:只要是符合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");
}
}