[题解]CF1060F Shrinking Tree

思路

对每个点分别计算概率,不妨令当前计算的点 \(r\) 成为这棵树的根。

将删掉一条边的操作转化为加入边,为每一个点初始赋一个互不相同的颜色,每次添加一条边将合并两个连通块成为一个连通块,同时在两个连通块的颜色中等概率选取其中一个作为新连通块的颜色。

考虑一个点 \(u\) 并入 \(r\) 所在连通块之后的影响。在这个操作之前,\(u\) 子树内的合并后对于颜色的选取对答案没有任何影响,因为最终这个连通块将与 \(r\) 合并然后颜色变为 \(r\) 的颜色。在这个操作之后,\(u\) 与其儿子节点 \(v\) 的子树合并必须使新连通块颜色为 \(u\),因为钦定了最后颜色一定为 \(r\),所以 \(v\) 最终也要变成 \(r\) 的颜色,即 \(u\) 的颜色(\(u\) 已经和 \(r\) 合并)。

接下来考虑 dp,定义 \(dp_{u,i}\) 表示在 \(u\) 子树中已经合并了 \(i\) 次,并在第 \(i + 1\) 次合并前将 \(u\) 并入 \(r\) 所在连通块同时变成 \(r\) 颜色的方案数。

转移时考虑 \((u,v)\) 边的贡献,记 \(f_{v,i}\) 表示在 \(u\) 子树内已经合并 \(i\) 次后,并入 \(r\) 连通块,\(v\) 并入 \(u\) 的方案数,讨论并入时的时间:

  1. 在第 \(i\) 次合并之前,因为对并入后的颜色没有要求,所以贡献为 \(dp_{v,i - 1} \times i\)
  2. 在第 \(i\) 次合并之后,因为要求 \(v\) 并入后和 \(u\) 颜色一致,所以要带 \(\frac{1}{2}\) 系数,贡献为 \(\sum_{j \geq i}\frac{dp_{v,j}}{2}\)

综上,转移为 \(f_{v,i} = dp_{v,i - 1} \times i + \sum_{j \geq i}\frac{dp_{v,j}}{2}\),容易用前缀和将 \(f\) 的转移优化到 \(\Theta(n^2)\)

最后合并 \(dp_{u,i}\)\(f_{v,j}\)\(dp'_{u,i}\),因为两连通块中未合并和已合并的边的内部相对顺序确定,因此有:

\[dp_{u,i} \times f_{v,j} \times \binom{i + j}{i} \times \binom{sz_u - i + sz_v - j}{sz_u - i} \rightarrow dp'_{u,i + j} \]

最后答案转回概率,即 \(\frac{dp_{r,0}}{(n - 1)!}\)

单次 dp 复杂度 \(\Theta(n^2)\),总复杂度 \(\Theta(n^3)\)

Code

#include <bits/stdc++.h>
#define re register
#define double long double

using namespace std;

const int N = 55;
int n,sz[N];
vector<int> g[N];
double fac[N],dp[N][N],pre[N],sum[N][N],f[N][N];

inline int read(){
    int r = 0,w = 1;
    char c = getchar();
    while (c < '0' || c > '9'){
        if (c == '-') w = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9'){
        r = (r << 3) + (r << 1) + (c ^ 48);
        c = getchar();
    }
    return r * w;
}

inline void init(int n){
    fac[0] = 1.0;
    for (re int i = 1;i <= n;i++) fac[i] = 1.0 * fac[i - 1] * i;
}

inline double C(int n,int m){
    if (n < m) return 0;
    else return fac[n] / fac[m] / fac[n - m];
}

inline void dfs(int u,int fa){
    sz[u] = 0,dp[u][0] = 1;
    for (int v:g[u]){
        if (v == fa) continue;
        dfs(v,u);
        for (re int i = 0;i <= sz[u];i++) pre[i] = dp[u][i],dp[u][i] = 0;
        for (re int i = 0;i <= sz[v];i++) f[v][i] = (i ? dp[v][i - 1] : 0) * i + sum[v][i] / 2.0;
        for (re int i = 0;i <= sz[u];i++){
            for (re int j = 0;j <= sz[v];j++) dp[u][i + j] += pre[i] * f[v][j] * C(i + j,i) * C(sz[u] - i + sz[v] - j,sz[u] - i);
        } sz[u] += sz[v];
    } sz[u]++;
    for (re int i = sz[u];~i;i--) sum[u][i] = sum[u][i + 1] + dp[u][i];
}

int main(){
    init(n = read());
    for (re int i = 1,a,b;i < n;i++){
        a = read(),b = read();
        g[a].push_back(b); g[b].push_back(a);
    }
    for (re int u = 1;u <= n;u++){
        memset(dp,0,sizeof(dp));
        memset(sum,0,sizeof(sum));
        dfs(u,0); printf("%.10Lf\n",dp[u][0] / fac[n - 1]);
    }
    return 0;
}
posted @ 2025-11-10 17:25  WBIKPS  阅读(6)  评论(0)    收藏  举报