luogu P3343 [ZJOI2015]地震后的幻想乡

link: https://www.luogu.com.cn/problem/P3343

首先orz积分大佬

然而我并不会积分


在努(tou)力(kan)思(ti)考(jie)后终于做了出来
这题就是求最小生成树的最大边的期望🐎
假设边权相对大小已经知道,那么
设最小生成树的最大边的排名为 i i i的概率为 P ( i ) P(i) P(i),

很容易得到 a n s = ∑ i = 1 m i m + 1 ∗ P ( i ) ans=\sum\limits_{i=1}^m \frac{i}{m+1}*P(i) ans=i=1mm+1iP(i)

即 a n s = 1 m + 1 ∑ i = 1 m i ∗ P ( i ) 即ans=\frac{1}{m+1}\sum\limits_{i=1}^m i*P(i) ans=m+11i=1miP(i)

a n s = 1 m + 1 ∑ i = 1 m ∑ j = 1 i P ( i ) ans=\frac{1}{m+1}\sum\limits_{i=1}^m \sum\limits_{j=1}^iP(i) ans=m+11i=1mj=1iP(i)

a n s = 1 m + 1 ∑ i = 1 m ∑ j = i m P ( j ) ( 和 上 一 条 是 等 价 的 ) ans=\frac{1}{m+1}\sum\limits_{i=1}^m \sum\limits_{j=i}^mP(j)(和上一条是等价的) ans=m+11i=1mj=imP(j)()

考虑 ∑ j = i m P ( j ) \sum\limits_{j=i}^mP(j) j=imP(j)是什么意思,发现就是选了排名前 i − 1 i-1 i1条边,图还不连通的概率
这个怎么算?
就是 ∑ j = i m P ( j ) = 不 连 通 的 方 案 数 总 的 方 案 数 ( 加 入 i − 1 条 边 后 ) \sum\limits_{j=i}^mP(j)=\frac{不连通的方案数}{总的方案数}(加入i-1条边后) j=imP(j)=(i1)
方案数的计算可以考虑DP(子集DP)

设 d p [ S ] [ i ] [ 0 / 1 ] 表 示 点 集 为 S , 加 入 了 i 条 边 , 连 通 与 否 设dp[S][i][0/1]表示点集为S,加入了i条边,连通与否 dp[S][i][0/1]S,i,

s i z e [ S ] 表 示 点 集 S 中 的 边 的 数 量 size[S]表示点集S中的边的数量 size[S]S
易 得 d p [ S ] [ i ] [ 0 ] + d p [ S ] [ i ] [ 1 ] = C s i z e [ S ] i 易得dp[S][i][0]+dp[S][i][1]=C_{size[S]}^i dp[S][i][0]+dp[S][i][1]=Csize[S]i

连通的很容易转移

d p [ S ] [ i ] [ 1 ] = C s i z e [ S ] i − d p [ S ] [ i ] [ 0 ] dp[S][i][1]=C_{size[S]}^i - dp[S][i][0] dp[S][i][1]=Csize[S]idp[S][i][0]

对于不连通的我们可以先钦定一个点作为关键点(套路)
然后枚举关键点所在的连通块的点集
然后把这一块孤立
在转移即可

具体还是看代码ba

code:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n, m, size[1 << 11];
ll c[55][55], f[1 << 11][55][2];
int main() {
	scanf("%d%d", &n, &m);
	for(int i = 0; i <= m; i ++) c[i][0] = 1;
	for(int i = 1; i <= m; i ++)
		for(int j = 1; j <= i; j ++)
			c[i][j] = c[i - 1][j] + c[i - 1][j - 1];
	for(int i = 1; i <= m; i ++) {
		int u, v;
		scanf("%d%d", &u, &v);
		for(int S = 0; S < (1 << n); S ++) 
			if(((1 << (u - 1)) & S) && ((1 << (v - 1)) & S)) size[S] ++;
	}
	
	for(int S = 0; S < (1 << n); S ++) {
		int ha = S &  (- S);//ha作为关键点 
		for(int S0 = S & (S - 1); S0; S0 = (S0 - 1) & S) if(S0 & ha){//S0为关键点的连通块 
			for(int a = 0; a <= size[S0]; a ++)
				for(int b = 0; b <= size[S ^ S0]; b ++)
					f[S][a + b][0] += f[S0][a][1] * c[size[S ^ S0]][b]; //把S0这一块孤立,转移非连通的 
		}
		for(int a = 0; a <= size[S]; a ++) f[S][a][1] = c[size[S]][a] - f[S][a][0];//转移连通的 
	}
	
	long double ans = 0.0;
	for(int i = 0; i <= m; i ++) ans += f[(1 << n) - 1][i][0] * 1.0 / (c[m][i] * 1.0);//记得除总方案数 
	printf("%.6Lf", ans * 1.0 / (m * 1.0 + 1.0));//最后记得除m+1 
	return 0;
}

代码不长,但是…
感受到了被DP和期望支配的恐惧
😱

posted @ 2019-12-12 20:30  lahlah  阅读(36)  评论(0编辑  收藏  举报