bzoj 4639 期望 矩阵树定理

题意

有一个n个点m条边的图,每条边有长度和美丽值。求该图的所有最小生成树中美丽值的和的期望。
满足长度相同的边的数量不超过30。
n ≤ 10000 , m ≤ 200000 n\le10000,m\le200000 n10000,m200000

分析

显然长度不同的边的贡献是独立的。
那么我们可以把每一种距离的边拿出来,对每一个连通块分别处理。
枚举同一个连通块中的每一条边,用矩阵树定理算出一定包含这条边的最小生成树的数量,然后算期望即可。
求行列式的时候不能辗转相除,要用long double。

代码

#include
#include
#include
#include
#include
#include
#include
#define pb push_back

typedef long double LL;

const int N=10005;
const LL eps=1e-12;

int n,m,f[N],id[N],g[N],num[N];
struct edge{int x,y,d,v;}e[N*20];
LL ans,a[35][35];

int find(int x)
{
	if (f[x]==x) return x;
	else return f[x]=find(f[x]);
}

int get(int x)
{
	if (g[x]==x) return x;
	else return g[x]=get(g[x]);
}

bool cmp(edge a,edge b)
{
	return a.d<b.d;
}

bool cmpf(int x,int y)
{
	return find(e[x].x)<find(e[y].x);
}

LL det(int n)
{
	for (int i=1;i<=n;i++)
	{
		int l=i;
		for (int j=i;j<=n;j++) if (a[j][i]) {l=j;break;}
		if (l!=i) for (int j=i;j<=n;i++) std::swap(a[l][j],a[i][j]);
		for (int j=i+1;j<=n;j++)
			if (fabs(a[j][i])>eps)
			{
				LL w=a[j][i]/a[i][i];
				for (int k=i;k<=n;k++) a[j][k]-=a[i][k]*w;
			}
	}
	LL ans=1;
	for (int i=1;i<=n;i++) ans=(LL)ans*a[i][i];
	return ans<0?-ans:ans;
}

void solve(int l,int r)
{
	int tot=0;
	for (int i=l;i<=r;i++)
	{
		int x=get(e[id[i]].x),y=get(e[id[i]].y);
		if (!num[x]) num[x]=++tot;
		if (!num[y]) num[y]=++tot;
		x=num[x];y=num[y];
		a[x][x]+=1;a[y][y]+=1;a[x][y]-=1;a[y][x]-=1;
	}
	LL ss=det(tot-1);
	for (int i=1;i<=tot;i++)
		for (int j=1;j<=tot;j++)
			a[i][j]=0;
	for (int i=l;i<=r;i++) num[get(e[id[i]].x)]=num[get(e[id[i]].y)]=0;
	for (int k=l;k<=r;k++)
	{
		int p=get(e[id[k]].x),q=get(e[id[k]].y);
		tot=0;
		for (int i=l;i<=r;i++)
		{
			int x=get(e[id[i]].x),y=get(e[id[i]].y);
			if (x==y||x==p&&y==q||x==q&&y==p) continue;
			if (x==p) x=q;
			if (y==p) y=q;
			if (!num[x]) num[x]=++tot;
			if (!num[y]) num[y]=++tot;
			x=num[x];y=num[y];
			a[x][x]+=1;a[y][y]+=1;a[x][y]-=1;a[y][x]-=1;
		}
		LL tt=det(tot-1);
		ans+=(LL)1.0*tt/ss*e[id[k]].v;
		for (int i=1;i<=tot;i++)
			for (int j=1;j<=tot;j++)
				a[i][j]=0;
		for (int i=l;i<=r;i++) num[get(e[id[i]].x)]=num[get(e[id[i]].y)]=0;
	}
}

int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;i++) scanf("%d%d%d%d",&e[i].x,&e[i].y,&e[i].d,&e[i].v);
	std::sort(e+1,e+m+1,cmp);
	for (int i=1;i<=n;i++) f[i]=g[i]=i;
	for (int l=1;l<=m;l++)
	{
		int r=l;
		while (r<m&&e[r+1].d==e[r].d) r++;
		for (int i=l;i<=r;i++)
		{
			int x=e[i].x,y=e[i].y;
			if (find(x)!=find(y)) f[find(x)]=find(y);
		}
		int tot=0;
		for (int i=l;i<=r;i++) if (get(e[i].x)!=get(e[i].y)) id[++tot]=i;
		std::sort(id+1,id+tot+1,cmpf);
		for (int p=1;p<=tot;p++)
		{
			int q=p;
			while (q<tot&&find(e[id[q+1]].x)==find(e[id[q]].x)) q++;
			solve(p,q);
			p=q;
		}
		for (int i=l;i<=r;i++)
		{
			int x=e[i].x,y=e[i].y;
			if (get(x)!=get(y)) g[get(x)]=get(y);
		}
		l=r;
	}
	printf("%.5lf\n",(double)ans);
	return 0;
}

你可能感兴趣的:(矩阵树定理)