题目传送门
题目大意
给定一棵$n$个点的带标号树,不断执行以下操作:
- 等概率选取一条边
- 删掉这条边的它的两个端点
- 新建一个点和之前与这两个点邻接的点连边,它的标号从这两个被删去的点中等概率选取。
问最后一个点的标号是$1, 2, \cdots, n$的概率。
现在我发现我不但不会设计状态,还不会设计转移,一道不错的题目。(cf的新功能真棒,成功过滤了傻逼题)
不难想到考虑每个点分别作为根的时候的答案。
每次删边可以看作将一个点的标号继承给另一个点, 因此根的不同子树相互不影响。
注意到选择边的方案数一定是$(n - 1)!$,所以将考虑的概率乘上$(n - 1)!$,最后再除掉。(这样不用考虑选边的概率,其实这里换成直接求方案数也可以,只不过可能怕被卡精度)
设$f_{i, j}$表示在当根的标号传给点$i$的时候,$i$的子树内还剩下$j$条边的时候,根标号被保留下来的概率乘上$(n - 1)!$后的结果。
最终我们想要的答案在$f_{root, n - 1}$中。
假设当前我们知道$i$和它部分子树的答案,考虑合并它的下一个以$u$为根的子树。
这里需要讨论一下$(i, u)$这条边在根标记传到$i$之前还是传到$i$之后被删掉的。
假设现在考虑根标记传到$i$的时候,$u$的子树中的所有边和$(u, i)$中还剩下$x$条边,根标记传到$u$的时候,$u$的子树内还剩下$y$条边。
设根标记传到$i$的时候,$u$的子树内的所有边和$(u, i)$中还剩下$x$条边,并且根节点的标号被保留的概率乘$(n - 1)!$的结果为$h_{x}$
- 如果$(i, u)$是在根标记传到$i$之前被删掉的,当根标记传到$i$的时候,根标记也传到了$u$,此时一定满足$x = y$。再考虑$(i, u)$的删除时间,显然它可以插在$u$被删除的$size_{u} - 1 - x$条边构成的序列中任何一个位置。这一部分对$h_{x}$的贡献为$(size_{u} - x)f_{u, x}$。
- 如果$(i, u)$是在根标号传到$i$之后被删掉的,当根标记传到$i$之后,$u$子树内可能还会删边,所以根标记传到$u$的时候,$u$子树内剩下的边数需要小于$x$(因为当根标记传到$i$的时候$(u, i)$还存在)。当这条边被删除的时候,根标号就传给给$u$,这个事件发生的概率是$\frac{1}{2}$,并且这条边在$u$的子树内所有边删除结束后删除掉的。因此这一部分对$h_{x}$的贡献为$\frac{1}{2}f_{u, y}$。
然后做一个背包合并。注意一下,合并两个子树的时候,需要为删边序列分配顺序以及被还被删去的删去的边的删去顺序(之前我们只是考虑它们的相对顺序)。所以还需要乘上两个组合数。
Code
1 /** 2 * Codeforces 3 * Problem#1060F 4 * Accepted 5 * Time: 31ms 6 * Memory: 100k 7 */ 8 #include <iostream> 9 #include <cstdlib> 10 #include <cstdio> 11 #include <vector> 12 using namespace std; 13 typedef bool boolean; 14 typedef long double ld; 15 16 const int N = 55; 17 18 template <typename T> 19 void pfill(T* pst, const T* ped, T val) { 20 for ( ; pst != ped; *(pst++) = val); 21 } 22 23 int n; 24 int sz[N]; 25 ld f[N][N]; 26 ld C[N][N]; 27 vector<int> g[N]; 28 29 inline void init() { 30 scanf("%d", &n); 31 for (int i = 1, u, v; i < n; i++) { 32 scanf("%d%d", &u, &v); 33 g[u].push_back(v); 34 g[v].push_back(u); 35 } 36 } 37 38 void dp(int p, int fa) { 39 static ld h[N], tmp[N]; 40 sz[p] = 1, f[p][0] = 1; 41 for (int i = 0, e; i < (signed) g[p].size(); i++) { 42 if ((e = g[p][i]) == fa) 43 continue; 44 dp(e, p); 45 int se = sz[e]; 46 for (int x = 0; x <= se; x++) { 47 h[x] = 0; 48 for (int y = 0; y < x; y++) 49 h[x] += f[e][y] * 0.5; 50 h[x] += f[e][x] * (se - x); 51 } 52 int sum = se + sz[p]; 53 pfill(tmp, tmp + sum + 1, (ld)0); 54 for (int x = 0; x < sz[p]; x++) 55 for (int y = 0; y <= se; y++) 56 tmp[x + y] += f[p][x] * h[y] * C[x + y][x] * C[sum - 1 - x - y][se - y]; 57 copy(tmp, tmp + sum + 1, f[p]); 58 sz[p] = sum; 59 } 60 /* 61 cerr << p << '\n'; 62 for (int i = 0; i < sz[p]; i++) 63 cerr << f[p][i] << " "; 64 cerr << '\n'; 65 */ 66 } 67 68 inline void solve() { 69 ld fac = 1; 70 for (int i = 2; i < n; i++) 71 fac = fac * i; 72 C[0][0] = 1; 73 for (int i = 1; i <= n; i++) { 74 C[i][0] = C[i][i] = 1; 75 for (int j = 1; j < i; j++) 76 C[i][j] = C[i - 1][j] + C[i - 1][j - 1]; 77 } 78 for (int i = 1; i <= n; i++) { 79 pfill(f[1], f[n + 1], (ld)0); 80 dp(i, 0); 81 printf("%.9lf\n", (double) (f[i][n - 1] / fac)); 82 } 83 } 84 85 int main() { 86 init(); 87 solve(); 88 return 0; 89 }