[ZJOI2015]地震后的幻想乡
[ZJOI2015]地震后的幻想乡
给定一张图,每条边的边权在 \([0,1]\) 中随机,求最小生成树的最大边权的期望。
\(n\le 10,m\le \frac{n(n-1)}{2}\)
Solution
这个题大概代表了七月份的一点学习记录,今天准备整理一下。不过按照这个方法推下来好 easy(无脑) 啊
对于连续型随机变量,我们一般使用概率密度函数 \(f(x)\) 来描述它取值的概率,其一定区域的积分可以表示取值为连续的一段区间的概率。
例如,论证 \(\mathbb{E}(\max\{x_1,x_2...x_n\})=\frac{n}{n+1}\) 时,我们先定义 \(F(x)=\max\{x_1...x_n\}\le x\),那么容易得到 \(F(x)=x^n\)
我们不难观察到,\(F(x)\) 的导数就是其对应的密度函数 \(f(x)\)
所以我们得到 \(f(x)=x^{n-1}n\)
容易观察得到,我们计算的期望为:
接下来我们考虑证明题面给出的性质,注意到我们计算的是集合的 \(\min_k\),所以我们可以考虑通过拓展 \(\min-\max\) 容斥来计算答案:
设 \(m=n-k\)
于是得证。
最后一步是分部积分的结论,下面也给出证明:
考虑:
设 \(B(a+b,b)=\int_0^1 x^a(1-x)^b\),那么不难发现:
- \(f(x)=\frac{x^a}{a+1},g(x)=(1-x)^b\)
观察到 \(B(a+b,0)=\int_0^1 x^{a+b}=\frac{1}{a+b+1}\),不难得到 \(B(a+b,b)=\frac{b!}{(a+b+1)^{\underline{b+1}}}=\frac{a!b!}{(a+b+1)!}\)
至此,命题得证。
注意到答案是:
构造 \(g(x)=P(X\le x)\),那么 \(P(X=x)\) 为 \(g(x)\) 的导数。
于是答案即为:
不难发现答案即为:
后者等价于仅保留小于 \(x\) 的边有此图联通。
可以基于这样的考量,我们给每条边赋予 \(0/1\) 的权值表示他是否小于等于 \(x\),如果为 \(1\) 那么小于等于 \(x\),不妨假设有 \(a\) 条 \(1\) 边和 \((m-a)\) 条 \(0\) 边,此时概率角度的贡献为:
对于某个具体的 \(a\),我们相当于计算:
于是我们成功的再次通过分部积分将此问题转换为给边染色,恰好染 \(a\) 条黑色边使得这张图联通的方案数。
等价于求这张图的恰好 \(a\) 条边的联通子图的数量。
经典的容斥手段是对于子集 \(S\) 先任意保留边,在枚举 \(1\) 号点所在的连通块,这里我们还需要枚举 \(1\) 所在的连通块所保留的边数,此时外部的边数是固定的,然后转移,复杂度为 \(\mathcal O(3^n\cdot m^2)\)。
不难发现转移的时候的卷积为子集卷积,可以通过 FWT 处理做到 \(\mathcal O(2^nn^2\cdot m^2)\),不过意义不大。
\(Code:\)
#include<bits/stdc++.h>
using namespace std ;
#define Next( i, x ) for( register int i = head[x]; i; i = e[i].next )
#define rep( i, s, t ) for( register int i = (s); i <= (t); ++ i )
#define drep( i, s, t ) for( register int i = (t); i >= (s); -- i )
#define re register
int gi() {
char cc = getchar() ; int cn = 0, flus = 1 ;
while( cc < '0' || cc > '9' ) { if( cc == '-' ) flus = - flus ; cc = getchar() ; }
while( cc >= '0' && cc <= '9' ) cn = cn * 10 + cc - '0', cc = getchar() ;
return cn * flus ;
}
const int N = 10 + 5 ;
const int M = 60 ;
int n, m, lim, g[1 << 11] ;
long double f[1 << 11][M], C[M][M], fac[M] ;
signed main()
{
n = gi(), m = gi() ; int x, y ;
rep( i, 1, m )
x = gi() - 1, y = gi() - 1,
++ g[(1 << x) | (1 << y)] ;
lim = (1 << n) ;
for(re int k = 1; k < lim; k <<= 1 )
rep( i, 0, lim ) if(i & k) g[i] += g[i ^ k] ;
C[0][0] = 1, f[0][0] = 1, fac[0] = 1 ;
rep( i, 1, m ) rep( j, 0, i ) C[i][j] = (!j) ? 1 : C[i - 1][j - 1] + C[i - 1][j] ;
rep( i, 1, m + 1 ) fac[i] = fac[i - 1] * i ;
for(re int S = 1; S < lim; ++ S) {
int p = n ;
rep( j, 0, n - 1 ) if((1 << j) & S) { p = j ; break ; }
rep( j, 0, m ) f[S][j] = C[g[S]][j] ;
for(re int i = S; i; i = (i - 1) & S) {
if((!(i & (1 << p))) || (i == S)) continue ;
int u = S ^ i, l = g[i], r = g[u] ;
rep( j, 0, l ) rep( k, 0, r )
f[S][j + k] -= f[i][j] * C[r][k] ;
}
}
long double Ans = 1 ; -- lim ;
rep( j, 0, m ) Ans -= f[lim][j] / fac[m + 1] * fac[j] * fac[m - j] ;
printf("%.6Lf\n", Ans ) ;
return 0 ;
}