题解 P13020 [GESP202506 八级] 遍历计数

· · 题解

题目分析

题目要求计算一棵树所有可能的 DFS 遍历序列数量。由于 DFS 的起点选择和子节点遍历顺序都是任意的,因此需要考虑所有可能的排列组合。并且根是不确定的,所以考虑换根 DP

算法思路

采用树形 DP 来解决这个问题,主要分为两个 DFS 过程:

  1. 状态定义

    • f[x]:以x为根的子树,不考虑父节点时的DFS序列数
    • dp[x]:以x为根的子树,考虑父节点时的DFS序列数
    • cnt[x]:x的子节点数量
  2. 转移方程

    • f_x = \prod_{y\in son_x}dp_y

完整代码

#include<bits/stdc++.h>
using namespace std;

#define int long long
const int M=1e5+5,mod=1e9;
int n,res,fac[M],f[M],dp[M],cnt[M];
vector<int>g[M],R[M];
// g[]存储树的邻接表,R[]用于存储后缀积

// 预处理阶乘数组
inline void init() {
    fac[0] = 1;
    for(int i=1;i<=n;++i)
        fac[i] = fac[i-1] * i % mod;
}

inline void dfs1(int x,int fa) {
    f[x] = dp[x] = 1;
    for(auto y:g[x]) {
        if(y == fa) continue;
        dfs1(y, x), cnt[x]++;
        (f[x] *= dp[y]) %= mod;
    } 
    dp[x] = f[x] * fac[cnt[x]] % mod;
    // 子树排列方式数目为f[x]乘以子节点数目的阶乘
}

inline void dfs2(int x,int fa,int val) {
    // 计算当前节点作为根的总遍历方式数目并累加到结果中
    if(x == 1) (res += dp[x]) %= mod;
    else (res += f[x] * val % mod * fac[cnt[x]+1] % mod) %= mod;

    // 预处理后缀积数组R,用于快速计算右侧兄弟子树的贡献
    for(int i=0;i<=g[x].size();++i)
        R[x].emplace_back(1);
    for(int i=g[x].size()-1;i>=0;--i) {
        if(g[x][i] == fa) R[x][i] = R[x][i+1];
        else R[x][i] = R[x][i+1] * dp[g[x][i]] % mod;
    }

    int L = 1; // 前缀积
    for(int i=0;i<g[x].size();++i) {
        int y = g[x][i];
        if(y == fa) continue;

        // 计算传递给子节点的信息:左侧兄弟子树的贡献 L * 右侧兄弟子树的贡献 R[i+1] * 父节点信息 val
        if(x == 1) dfs2(y, x, L * R[x][i+1] % mod * fac[cnt[x]-1] % mod);
        else dfs2(y, x, L * R[x][i+1] % mod * fac[cnt[x]] % mod * val % mod);

        (L *= dp[y]) %= mod;
    } 
}

signed main() {
    scanf("%lld", &n),init();

    // 读入树的边,构建邻接表
    for(int i=1,u,v;i<n;++i) {
        scanf("%lld%lld", &u, &v);
        g[u].emplace_back(v);
        g[v].emplace_back(u);
    }

    // 两次DFS处理
    dfs1(1, 0); // 以节点1为根进行第一次DFS
    dfs2(1, 0, 0); // 以节点1为根进行第二次DFS

    printf("%lld", res); // 输出结果
    return 0;
}

复杂度分析

时间复杂度 O(n),两次 DFS 遍历树的每个结点和边。

空间复杂度:O(n),主要用于存储树的结构和动态规划数组。