[ZJOI2015]地震后的幻想乡

Description

给出一张n个点m条边的无向图,第i条边有一个在[0,1]内独立随机的权值ei
问这张图的最小生成树的最大边权的期望
n<=10,m<=n*(n-1)/2

Solution

设f(x)表示只用边权 可以发现,f(k)=P(x>=k),根据概率公式我们知道答案是 ∫ 0 1 f ( x ) d x \int_0^1f(x)dx 01f(x)dx
显然f(x)是一个关于x的多项式,我们考虑怎么求
设F[s]表示点集S连通的概率,这个很经典,可以枚举包含S中任意一个点的子集来转移
那么答案就是1-F[all]
注意这种做法会被卡精度,要用__float128

Code

#include 
#include 
#include 
#include 
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

typedef vector<__float128> poly;

poly operator + (poly a,poly b) {
	poly c;c.resize(max(a.size(),b.size()));
	for(int i=0;i<c.size();i++) c[i]=a[i]+b[i];
	return c;
}

poly operator - (poly a,poly b) {
	poly c;c.resize(max(a.size(),b.size()));
	for(int i=0;i<c.size();i++) c[i]=0;
	for(int i=0;i<a.size();i++) c[i]+=a[i];
	for(int i=0;i<b.size();i++) c[i]-=b[i];
	while (!c.empty()&&!c[c.size()-1]) c.pop_back();
	return c;
}

poly operator * (poly a,poly b) {
	poly c;c.resize(a.size()+b.size()-1);
	for(int i=0;i<c.size();i++) c[i]=0;
	for(int i=0;i<a.size();i++)
		for(int j=0;j<b.size();j++)
			c[i+j]+=a[i]*b[j];
	return c;
}

const int S=(1<<10)+5,N=11,M=N*N;

poly f[S],pw[M];
int n,m,x,y,e[N];

int count(int s) {
	int ret=0;
	for(;s;s>>=1) ret+=s&1;
	return ret;
}

int main() {
	scanf("%d%d",&n,&m);
	fo(i,1,m) {
		scanf("%d%d",&x,&y);
		e[x]|=1<<y-1;e[y]|=1<<x-1;
	}

	pw[0].resize(1);pw[0][0]=1;
	pw[1].resize(2);pw[1][0]=1;pw[1][1]=-1;
	fo(i,2,m) pw[i]=pw[i-1]*pw[1];
	fo(s,1,(1<<n)-1) {
		f[s].resize(1);f[s][0]=1;
		int x=s&-s,tw=s-x;
		for(int t=tw;t;t=(t-1)&tw) {
			if ((t|x)==s) continue;
			int cnt=0;
			fo(i,1,n) if ((t|x)&(1<<i-1)) cnt+=count(e[i]&(s^(t|x)));
			f[s]=f[s]-f[t|x]*pw[cnt];
		}
		if (x==s) continue;
		int cnt=0;
		fo(i,1,n) if (x&(1<<i-1)) cnt+=count(e[i]&(s^x));
		f[s]=f[s]-f[x]*pw[cnt];
	}
	poly ans;ans.resize(1);ans[0]=1;
	ans=ans-f[(1<<n)-1];
	__float128 ret=0;
	for(int i=0;i<ans.size();i++) ret+=ans[i]*1.0/(i+1);
	printf("%.6lf\n",(double)ret);
	return 0;
}

你可能感兴趣的:(状压dp,概率与期望)