Codeforces 1060F Shrinking Tree - 动态规划 - 组合数学

题目传送门

  传送门I

  传送门II

  传送门III

题目大意

  给定一棵$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 }

猜你喜欢

转载自www.cnblogs.com/yyf0309/p/9901508.html