牛客 - 牛牛的树行棋

题目描述

牛牛的树行棋

解法:SG函数+DFS(C++)

详细参考 Lskkkno1的解 和 段三园的小迷弟的解

我们先介绍关于 SG 函数的两个结论:

  • 当前局面的 SG 值为 0,先手必败,否则先手必胜
  • 若一个游戏是多个独立游戏组成的,那么当前局面的 SG 值是多个独立游戏 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 SGixxor 的位置

于是,我们通过一个计数数组 c n t [ . . . ] cnt[...] cnt[...],记录当前根节点下的全部子节点出现的 SG 值的次数,然后求和 c n t [ S G i ⊕ x x o r ] cnt[SG_i \oplus xxor] cnt[SGixxor] 就是我们想要的结果啦

当然,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;
}

你可能感兴趣的:(题解)