原题链接:
题解:
首先需要明确一个SG函数理论:多个独立局面的SG值,等于这些局面SG值的异或和。
这个定理是SG的关键,最初在Nim游戏中,每个堆都可以被视为一个独立局面,为了确定是先手必赢态还是先手必输态,我们就需要求得所有独立局面的SG值,而这个值就等于这些局面SG值的异或和。也即:
那么在本情景下,每个堆(独立局面A)都可以进行拆分,也即A可以拆分得到很多情况的独立局面B,例如10拆分为①{1、9}②{2、8}③{3、7}④{4、6}等等。
那么这个10的sg值就需要通过这些B的sg值的异或和得到。
代码:
#include<bits/stdc++.h>
using namespace std;
int n, res;
const int N = 110;
int a[N], f[N];
unordered_set<int> S;
int sg(int x) {
if (f[x] != -1) return f[x];
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(){
cin >> n;
memset(f, -1, sizeof(f));
for (int i = 1;i <= n;i++) {
int x;cin >> x;
res ^= sg(x);
}
cout << (res ? "Yes" : "No");
}
疑惑点:
为啥这里的S可以开全局?
因为这题中原堆拆分成的两个较小堆小于原堆即可,因此任意一个较小堆的拆分情况会被完全包含在较大堆中,因此S可以开全局。
你可以认为是先将堆按石子数量从大到小排序,然后在第一次sg(max)的过程中就已经得到了后续所有x的f[x]值。
这是一种包含关系,大的包含小的所有情况,而之前的集合-Nim游戏堆之间没有包含关系,因此不能使用全局S。
#include<bits/stdc++.h>
using namespace std;
int n, res;
const int N = 110;
int a[N], f[N];
unordered_set<int> S;
int sg(int x) {
if (f[x] != -1) return f[x];
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() {
cin >> n;
memset(f, -1, sizeof(f));
for (int i = 1;i <= n;i++) cin >> a[i];
sort(a + 1, a + 1 + n, greater<int>());//先排序得到后续所有元素的f值,减少计算次数
for (int i = 1;i <= n;i++) res ^= sg(a[i]);
cout << (res ? "Yes" : "No");
}