牛牛的树行棋
详细参考 Lskkkno1的解 和 段三园的小迷弟的解
我们先介绍关于 SG 函数的两个结论:
那么,对这道题而言,每一枚棋子其实都是独立的,也就是说,当前局面的多个独立游戏就是每一枚棋子,当前局面的 SG 值就是每一枚棋子的 SG 值的异或和。
那么一枚棋子的 SG 值怎么求呢?
若一枚棋子在叶子节点(后继状态是空集),它的 SG 值为 0
若一枚棋子的后继节点只有叶子节点,它的 SG 值为 1
若一枚棋子的后继节点 SG 值最大为 1 (那么肯定也有 0),它的 SG 值为 2
若一枚棋子的后继节点 SG 值最大为 2 (那么肯定也有 1, 0),它的 SG 值为 3
⋯ ⋯ \cdots \cdots ⋯⋯
就是说,除叶节点外,每个节点的 SG 值等于 m a x ( 子 树 的 S G 值 ) + 1 max(子树的 SG 值) + 1 max(子树的SG值)+1
根据上面的结论,求出所有 SG 值的异或和 x x o r xxor xxor,如果 x x o r ! = 0 xxor != 0 xxor!=0,先手有必赢策略,否则必输
那么知道必赢之后该怎么求解方法数呢?
因为 SG 值为 0 的局面是必输的,也就是说,我们需要在第一次移动之后使得局面的 SG 值一定要等于 0
考虑当前要移动的棋子为 i i i,它的 SG 值为 S G i SG_i SGi,且令移动之前局面的 SG 值为 x x o r xxor xxor,要使移动后总的 SG 值为 0,这枚棋子需要移动到 SG 值等于 S G i ⊕ x x o r SG_i \oplus xxor SGi⊕xxor 的位置
于是,我们通过一个计数数组 c n t [ . . . ] cnt[...] cnt[...],记录当前根节点下的全部子节点出现的 SG 值的次数,然后求和 c n t [ S G i ⊕ x x o r ] cnt[SG_i \oplus xxor] cnt[SGi⊕xxor] 就是我们想要的结果啦
当然,cnt[sg[x]]++; cnt[sg[x]]--;
是为了保证 DFS 到达叶节点回溯时走过的路已经计算完毕,如果不减掉那就会重复计算
#include
using namespace std;
const int N = 5e5+10;
vector<int> e[N];
int n, u, v, sum, xxor, sg[N<<1], cnt[N<<1];
void dfs1(int x, int pre){
// 计算每个节点的 sg 值,判断先手是否能赢
for(auto i: e[x])
{
if(i!=pre)
{
dfs1(i, x);
sg[x] = max(sg[x], sg[i]+1);
}
}
xxor ^= sg[x];
}
void dfs2(int x, int pre){
// 求必赢策略中的第一步的方法数
cnt[sg[x]]++;
sum += cnt[sg[x]^xxor];
for(auto i: e[x])
if(i!=pre) dfs2(i, x);
cnt[sg[x]]--;
}
int main()
{
cin >> n;
for(int i=1;i<n;i++)
{
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
dfs1(1, -1);
if(xxor)
{
dfs2(1, -1);
cout << "YES" << endl;
cout << sum << endl;
}
else cout << "NO" << endl;
return 0;
}