Distributing Integers 树的拓扑序计数

https://vjudge.net/problem/AtCoder-abc160_f

题意:
求以每个点为根节点的树的拓扑序计数。

思路:
是一个挺经典的问题。
我们考虑自下而上的树形dp:假设我们当前点在u,我们已经求出来了\(dp[v],v∈sons_u\)
现在要求u的方案数,我们考虑在u放上1,然后剩下\(sz[u]−1\)个数生成一个排列依次给每个结点赋值。之后类似于一个可重集的排列问题,对于每个结点,我们要除以\(sz[v]!\),就相当于我们从\(sz[u]−1\)个数中拿出来一些数给\(v\)这颗子树赋值,不考虑顺序的情况下,这些数不同的方案数。接下来就考虑上顺序,乘以一个\(dp[v]\)就行。所以总的方案数为:

\[(sz[u]−1)!⋅∏ \frac {dp[i]}{sz[i]!},u∈father(i) \]

当然,还有更加简单的公式,我们不用考虑dp,因为我们发现首位固定最小的排列数为\((n−1)!\)。那么对于以u为根节点的子树答案为:

\[sz[u]!∏ \frac 1 {sz[i]},u∈father(i) \]

这两个式子是等价的,我们可以直接归纳证明。

这样就求出来了以一个点作为根节点的情况,求所有点作为根结点的情况直接换根跑一下就行。

const int maxn = 2e5 + 10;
const int mod = 1e9 + 7;
int head[maxn], to[maxn << 1], nxt[maxn << 1], ecnt = 0;
inline void add(int u, int v) {
    to[++ecnt] = v; nxt[ecnt] = head[u]; head[u] = ecnt;
    to[++ecnt] = u; nxt[ecnt] = head[v]; head[v] = ecnt;
}
int fact[maxn], finv[maxn], inv[maxn];
void init() {
    fact[0] = fact[1] = finv[0] = finv[1] = inv[1] = 1;
    for(int i = 2; i < maxn; ++ i)  {
        fact[i] = fact[i - 1] * i % mod;
        inv[i] = (mod - mod / i) * inv[mod % i] % mod;
        finv[i] = finv[i - 1] * inv[i] % mod;
    }
}
int ans[maxn], sz[maxn];
int dfs(int now, int fa) {
    sz[now] = 1;
    for(int i = head[now]; i; i = nxt[i]) {
        if(to[i] == fa) continue;
        sz[now] += dfs(to[i], now);
    }
    return sz[now];
}

int n;
void dfs2(int now, int fa) {
    for(int i = head[now]; i; i = nxt[i]) {
        if(to[i] == fa) continue;
        ans[to[i]] = ans[now] * sz[to[i]] % mod * inv[n - sz[to[i]]] % mod;
        dfs2(to[i], now);
    }
}
void run() {
    for(int i = 1; i <= n; ++ i)    head[i] = 0;    ecnt = 0;
    for(int i = 1; i < n; ++ i) {
        int u = rd(), v = rd();
        add(u, v);
    }
    dfs(1, 1);
    ans[1] = fact[n];
    for(int i = 1; i <= n; ++ i)    ans[1] = ans[1] * inv[sz[i]] % mod;
    dfs2(1, 1);
    for(int i = 1; i <= n; ++ i)    printf("%lld\n", ans[i]);
    return ;
}


posted @ 2021-07-14 15:58  wansheking  阅读(733)  评论(0)    收藏  举报