[CF1060F] Shrinking Tree

[题目链接]

http://codeforces.com/contest/1060/problem/F

[题解]

首先显然可以计算以每个点为根的答案。

直接求解概率比较困难 , 但由于总排列方案数是 \((N - 1)!\) , 因此不妨令 \(dp_{u , i}\) 表示以 \(u\) 为根的子树 , 保留最后 \(i\) 条边 , 根的标号不变的概率之和。

换言之 , \(DP\) 存的是 \(\sum_{p \ is \ sequence}{P(p)}\)

\(size_{u}\) 表示以 \(u\) 为根的子树大小。

考虑合并两棵子树 , 一棵树 \(u\)\(x\) 条保留边 , 另一棵有 \(y\) 条保留边。

那么贡献即为 \(f_{u , x} * g_{y} * {x + y \choose x} * {s_{u} + s_{v} - x - y \choose s_{u} - x}\)。 其中 , \(g_{y}\) 表示子树 \(v\) 保留了多少边。

考虑如何计算 \(g_{i}\) , 不妨考虑 \(f_{v , j}\) 对其贡献。

\(j > i\) 时 , 显然无贡献。

\(j = i\) 时 , 说明 \((u , v)\) 这条边必然在最后 \(j\) 条边之前合并完了 , 并且无需关心合并的标号问题 , 贡献为 \((size_{v} - j) * f_{v , j}\)

\(j < i\) 时 , 说明 \((u , v)\) 这条边必然在最后第 \(j\) 条边之前合并 , 且合并后标号为 \(u\)。 其贡献为 \(0.5 * f_{v , j}\)

这样我们就解决了合并问题 , 那么做一个近似于树上背包的 \(DP\) 即可。

时间复杂度 : \(O(N ^ 3)\)

[代码]

#include<bits/stdc++.h>
 
using namespace std;
 
typedef long long LL;
 
#define rep(i , l , r) for (int i = (l); i < (r); ++i)
 
const int MN = 1005;
 
struct Edge {
	int to , nxt;
} E[MN << 1];
 
int N , tot , head[MN] , siz[MN];
double fac[MN] , tmp[MN] , f[MN][MN] , g[MN];
 
inline void init() {
	fac[0] = 1;
	for (int i = 1; i <= N; ++i)
		fac[i] = fac[i - 1] * i;
}
inline void AddEdge(int u , int v) {
	E[++tot] = (Edge) {v , head[u]};
	head[u] = tot;
}
inline double binom(int n , int m) {
	return fac[n] / fac[m] / fac[n - m];
}
inline void DFS(int u , int fa) {
	siz[u] = 1; f[u][0] = 1;
	for (int i = 1; i <= N; ++i) f[u][i] = 0;
	for (int i = head[u]; i; i = E[i].nxt) {
		int v = E[i].to; if (v == fa) continue; DFS(v , u);
		for (int k = 0; k <= N; ++k) tmp[k] = g[k] = 0;
		for (int j = 0; j <= siz[v]; ++j)
		for (int k = 0; k <= j; ++k)
			if (k < j) g[j] += 0.5 * f[v][k];
			else g[j] += (siz[v] - k) * f[v][k];
		for (int j = 0; j < siz[u]; ++j)
		for (int k = 0; k <= siz[v]; ++k)
			tmp[j + k] += f[u][j] * g[k] * binom(j + k , j) * binom(siz[u] - 1 - j + siz[v] - k , siz[v] - k);
		siz[u] += siz[v];
		for (int k = 0; k < siz[u]; ++k) 
			f[u][k] = tmp[k];  
	}
}
int main() {
	 
	 scanf("%d" , &N);
	 for (int i = 1; i < N; ++i) {
	 	 int u , v; scanf("%d%d" , &u , &v);
	 	 AddEdge(u , v) , AddEdge(v , u);
	 }
	 init();
	 for (int i = 1; i <= N; ++i) {
	 	 DFS(i , 0);
	 	 printf("%.10lf\n" , f[i][N - 1] / fac[N - 1]);
	 }
     return 0;
}
posted @ 2021-02-24 13:22  evenbao  阅读(61)  评论(0编辑  收藏  举报