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;
}