F、牛牛的树行棋
博弈论sg函数与dfs。
前导知识sg函数:
SG函数:
首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、mex{2,3,5}=0、mex{}=0。
对于任意状态 x , 定义 SG(x) = mex(S),其中 S 是 x 后继状态的SG函数值的集合。如 x 有三个后继状态分别为 SG(a),SG(b),SG(c),那么SG(x) = mex{SG(a),SG(b),SG(c)}。 这样 集合S 的终态必然是空集,所以SG函数的终态为 SG(x) = 0,当且仅当 x 为必败点P时。
结论:当一个局面sg值为0时必败,否则对方必败。
sg定理:
那么整局游戏的sg值等于每个子游戏的sg值异或。
那么很显然,叶子节点sg值为0,因为不存在下一个状态。
而其余节点,就是全部子节点的sg值最大在加一。
如果我们求解到树上全部节点的sg异或的值(记为ans)为0说明先手必败。
否则,求解方案数。那么因为状态只能在自己和子节点之间转移。那么我们就要去寻找 sg[u] XOR sg[v] == ans的节点对数。v是u的子节点
假设我们求解的是x节点那么再用 k = ans XOR sg[x],再另外开一个计数器数组,记录当前根节点下的全部子节点出现的sg值次数
每次把sum += cnt[k]即可。
#include <bits/stdc++.h>
#pragma GCC optimize(2)
#pragma GCC optimize(3)
using namespace std;
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
typedef long long ll;
inline int read() {
int s = 0, w = 1; char ch = getchar();
while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); }
while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
return s * w;
}
const int N = 5e5 + 7; //大约19个1
vector<int> e[N];
int sg[N], cnt[1 << 20];
int sum, ans;
void dfs1(int u, int fa) {
for (auto v : e[u]) {
if (v == fa) continue;
dfs1(v, u);
sg[u] = max(sg[u], sg[v] + 1);
}
ans ^= sg[u];
}
void dfs2(int u, int fa) {
++cnt[sg[u]]; //入栈+1
int k = ans ^ sg[u];
sum += cnt[k];
for (auto v : e[u]) {
if (v == fa) continue;
dfs2(v, u);
}
--cnt[sg[u]];
}
int main() {
int n = read();
while (--n) {
int u = read(), v = read();
e[u].push_back(v);
e[v].push_back(u);
}
dfs1(1, 0);
if (ans) {
dfs2(1, 0);
printf("YES\n%d\n", sum);
}
else
puts("NO");
return 0;
}

京公网安备 11010502036488号