拒绝结论,直接打表
这种题目的中间结论——除非在看到题目之前就知道它——对于解题是没有意义的。只要能猜到最后的答案,就一定能以某种方式证明所有的结论。所谓的这些中间结论就变成了最终的答案的简单推论。比如本题中这个「局面的 SG 值是单个点的 SG 值的 Nim 和」就是这样的中间结论。
这样的中间结论不能成为思考的起点。
对于这种题目,思考的起点只能是打表。写一个暴力算 SG 值的代码:
constexpr int M = 4, N = 4;
auto check = [&](int val, int mai, int maj) -> bool {
auto dfs = [&](auto&& dfs, int i, int j) -> void {
if (!((val >> (i * N + j)) & 1)) return;
val ^= 1 << (i * N + j);
if (i) dfs(dfs, i - 1, j);
if (i + 1 < M) dfs(dfs, i + 1, j);
if (j) dfs(dfs, i, j - 1);
if (j + 1 < N) dfs(dfs, i, j + 1);
};
dfs(dfs, mai, maj);
return val == 0;
};
int idx = 0;
std::vector<int> state;
std::vector<int> nim(1 << (M * N));
for (int cr = 0; cr < (1 << (M * N)); ++cr) {
std::vector<int> nxt;
for (int pr = 0; pr < cr; ++pr) {
int diff = cr ^ pr;
int mai = 0, maj = 0;
for (int i = 0; i < M; ++i) {
for (int j = 0; j < N; ++j) {
if ((diff >> (i * N + j)) & 1) {
mai = std::max(mai, i);
maj = std::max(maj, j);
}
}
}
if (((diff >> (mai * N + maj)) & 1) && check(diff, mai, maj)) {
nxt.emplace_back(nim[pr]);
}
}
std::sort(nxt.begin(), nxt.end());
nxt.erase(std::unique(nxt.begin(), nxt.end()), nxt.end());
for (; nim[cr] < (int)nxt.size() && nim[cr] == nxt[nim[cr]]; ++nim[cr]);
}
多尝试几组
首先,第一个发现是,SG 值过于混乱,没有明显的规律。(如果你这一步就猜出了前文所述的中间结论,那你可以直接跳过本文。)
但是,这种题目,没有明显的独立子游戏的结构,SG 值没用。只需要考察必败状态就行。所以,输出所有必败状态。
然后,就可以发现,必败状态的数量一定是
于是,猜测所有
这一步可以惊喜地发现,这些基本状态都只有两个
比如,
0
HHH
HHH
=0
1
THT
HHH
=0
2
HTH
THH
=0
3
TTT
THH
=0
其中,第一个和第二个状态是基本状态,第三个是它们的组合。
对于更复杂的情形,可以打印出所有的只有两个
最后需要判断是否是必败状态,只要统计 每个等价类中的位置的个数是否为偶数 就可以了。
利用这些简单状态打个表,看一下位置的等价类具体长啥样。很容易发现,除了第
由此,总结出计算等价类标号的方法为:
直接计算即可。
代码就很简单:
int main() {
auto calc = [&](int x, int y) {
return (!x || !y) ? __builtin_ctz(x + y + 1) : x + y;
};
int t;
std::cin >> t;
for (; t; --t) {
int n, m;
std::cin >> n >> m;
std::bitset<200> st;
for (int i = 0; i < n; ++i) {
std::string s;
std::cin >> s;
for (int j = 0; j < m; ++j) {
if (s[j] == 'T') {
st.flip(calc(i, j));
}
}
}
std::cout << (st.any() ? "-_-" : "=_=") << '\n';
}
return 0;
}
当然,这样分析的结果就是别的题解中算出的 SG 函数值的对数。
所有别的题解提到的中间结论和本文猜出来的中间结论都可以在归纳地证明了最后得到的 SG 函数值或等价类函数之后,再漫不经心地证明。
但是,如果事先不知道这些中间结论,那么,其实它们也不是啥必需品。这就是本文想说明的核心观点。