题面: [ZJOI2015]地震后的幻想乡,首次发表在洛谷博客。
@Ymiracle 的题解已经给出了该做法的思路,写的很详细,但是在没什么斯特林反演的题目的情况下,似乎还是有那么一点难以理解,这篇题解主要是补充,尽量让一个没做过斯特林反演题目的人也可以看懂。
该讲的题意转化还是得讲:首先,题目的意思是图的每一条边的权值都在([0,1])间随机分布,求原图最小生成树瓶颈边的权值期望大小。 题目里已经提示我们(n)个([0,1])间的随机变量第(k)小的期望值是(frac{k}{m+1}),这其实就是想让我们考虑把权值的期望问题转化成排名的概率问题。
为什么这样说?我们可以从期望的定义入手:
虽然随机变量(X)的取值有无穷多个,但是瓶颈边的大小在(m)条边里必然有一个排名(排名是离散的!!),不妨枚举这个排名,就可以得到权值:
所以,我们就要求(p_k=P(mathrm{the rank of the edge=} k)). 那么,瓶颈边的排名为(k)是啥意思?就是说我们用了(k-1)条边,图不连通,现在我加入第(k)条边,图就联通了,这就代表我们加的这条边成为了瓶颈边。 也就是说,(p_k)表示有顺序地选(k)条边恰好联通原图的概率.
我本来想:这不就相当于让你求原图选(k)条边,问有多少种情况得到连通图吗。但显然不是的。有一个很简单的反例:
假设这就是原图,显然边全部都选是(k=5)条边联通原图的一个方案,但是这可千万不能算到(p_5)里面,因为(5)条边恰好联通原图的概率为(0),随便你最后加的是哪一条边,原图都已经联通了,就没有恰好这一说。
这时候一个很巧妙的处理方法就是前缀和,令
那么(p'_k)就代表 有顺序选(1)条边恰好联通原图的概率 (+) 有顺序选(2)条边恰好联通原图的概率 (+cdots+) 有顺序选(k)条边恰好联通原图的概率 (=) 有顺序选(k)条边原图联通的概率。
上述等式显然可以通过全概率公式来验证:
而有顺序选(k)条边原图联通的概率 (=) 无顺序选(k)条边原图联通概率!!!
这样,如果能求(p')数组,那么(p)数组就可以求。
上面这段可能有点难理解,不过下面就和概率期望没关系了。
现在我们得到另一个问题:给定一个图,求保留图中(k)条边,原图联通的概率(p'_k)。
考虑到(mleq 45),其保留若干条边的最大方案数(inom{45}{23})在(mathrm{long long})类型范围内,那么问题转到求解保留(k)条边,使原图联通方案数(F_{k}),显然有(p'_k=frac{F_k}{inom{m}{k}}).
这样的话问题就变得很经典了,对于和图连通性有关的计数问题,我们要考虑斯特林反演。 不妨设(f_{S,i,j})表示现在我们选了原图中(i)条边,把点集(S)划分成(j)个连通块的方案数,(g_{S,i,j})表示我们选了原图中(i)条边,点集(S)被钦定划分成(j)个连通块(事实上可能更多)的方案数。那么有如下关系:
这个式子对理解斯特林反演来说至关重要,一个很好的想法是:对比二项式反演。
在二项式反演里面,我们要算恰好(k)的方案,当然是令 (()钦定(k)的方案()=sum_{lgeq k}inom{l}{k}()恰好(l)的方案()),然后设法反演(/)递推求出答案。 为什么要这样做,那是因为钦定(k)的方案很好算。
钦定(k)啥意思?不就是我强选(k)个性质固定下来,然后剩下的随便来,使得真正的性质数量至少为(k)吗。
斯特林反演同样如此,既然(i)条边把点集(S)划分成(j)个连通块的方案数不好算,那么我们就钦定点集(S)被划分为(j)个连通块,然后再计算 在不影响钦定假设的前提下 随意地(i)条边的方案(即不在钦定划分开来的两个连通块之间加边),这样,实际上的连通块数量,就至少为(j)。
此时,我们的求和系数就是第二类斯特林数,因为任何一个真实的划分(k),都会对钦定的划分(j)产生(egin{Bmatrix}k \ jend{Bmatrix})次贡献,即:选子集方案数。
现在我们可以对原式进行斯特林反演:
斯特林反演的证明,可以在我的这篇博客里找到,这道题的斯特林反演的式子还稍有不同,要用到反演原理(博客里面(3.2)节)来转化。
要求(f_{mathrm{U},1sim m,1}),就是要求(g_{mathrm{U},1sim m,1sim n})。 现在考虑(g),显然,没有任何限制的集合划分方案数就是(mathrm{Bell})数,但是现在我们要在不连接已经划分集合的前提下加入(i)条边,不妨考虑子集(mathrm{dp})。
如果要直接算(g),那么至少要枚举(S,i,j),要枚举(S)的子集(T)表示新加入的一个集合,枚举(k)表示新集合里面选的边数,那么复杂度就至少要(mathcal{O}(3^nnm^2)),显然爆炸。
但是其实我们可以先算一个(h_{S,i,j}),表示点集(S)被钦定划分成(j)个连通块,这(j)个连通块里面一共有(i)条边可选的方案数. 可选的意思就是我加入一个连通块就把连通块里面的所有边都加到(mathrm{dp})数组的下标里面,表示我总共有这么多边可以用.
那么就会有
现在以同样的方式(mathrm{dp}),既不用枚举子集(T)当中的边数了,于是复杂度就降到了(mathcal O(3^n nm))已经可以通过。
具体地,令(e(T))表示原图里边点集(T)有多少条边,我们可以枚举一个包含(S)最小标号点的子集(T),那么:
如果要求集合之间是没有标号的(或者说无序的),那么强制使转移时加入的最后一个子集包括最小标号点是一个很好的避免算重的方式,代码可以这样写:
for (int S = 1; S < 1<<n; S++)
for (int i = 0; i <= e[S]; i++)
for (int j = 1; j <= sz[S]; j++) {
for (int T = S&(S-1), P; P = T|Lowbit(S), T; T = (T-1) & S & (S-1))
if ( i >= e[P] ) h[S][i][j] += h[S-P][i-e[P]][j-1];
h[S][i][j] += h[ S - Lowbit(S) ][ i - e[Lowbit(S)] ][j-1];
}
根据上面推的式子,应该容易写出下面的代码(略去预处理):
for (int i = 0; i <= m; i++)
for (int j = 1; j <= n; j++)
for (int k = i; k <= m; k++)
g[i][j] += h[(1<<n)-1][k][j] * c[k][i];
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
f[i] += ( (j&1) ? 1 : -1 ) * fac[j-1] * g[i][j];
for (int i = 0; i <= m; i++) p[i] = 1.0 * f[i] / c[m][i];
for (int i = m; i >= 1; i--) p[i] = p[i] - p[i-1];
for (int i = 1; i <= m; i++) Ans += p[i] * i / (m+1);
这样已经完全可以通过本题了。
然而@Ymiracle 大佬还提到了一种把系数((-1)^{k-1}(k-1)!)放到(mathrm{dp})里面的做法,暂时还没有搞懂怎么转移来处理系数((k-1)!),可能可以参考这篇博客。