JSOI 2008 最小生成树计数 题解

题目传送门

题目大意: 求一张图有多少棵最小生成树。

题解

最小生成树有个性质,就是每种长度的边的数量是固定的。

所以先随便找一棵最小生成树,考虑长度为 i i i 的边,先将长度不为 i i i 的最小生成树中的边加入进去,然后就形成了若干个连通块,然后剩下的问题就是用长度为 i i i 的边将这些连通块连成一棵树有多少种方案,显然用矩阵树定理就能求。

最后将每种长度的方案数乘起来就是答案了。

代码如下:

#include 
#include 
#include 
using namespace std;
#define maxn 110
#define mod 31011

int n,m,ans=1;
struct edge{int x,y,z;}e[1010];
int belong[maxn];bool v[1010];
struct BCJ{
	int fa[maxn];
	void init(){for(int i=1;i<=n;i++)fa[i]=i;}
	int findfa(int x){return x==fa[x]?x:fa[x]=findfa(fa[x]);}
	bool linked(int x,int y){return findfa(x)==findfa(y);}
	bool link(int x,int y)
	{
		x=findfa(x),y=findfa(y);
		if(x==y)return false;
		fa[y]=x;return true;
	}
}F;
bool cmp(edge x,edge y){return x.z<y.z;}
int dec(int x){return x<0?x+mod:x;}
int f[maxn][maxn];int det(int l)
{
	int re=1,fu=1;
	for(int i=1,p;i<=l;i++)
	{
		for(int j=i+1;j<=l;j++)
		while(f[j][i]!=0){
			p=f[i][i]/f[j][i];
			for(int k=i;k<=l;k++)f[i][k]=f[i][k]-f[j][k]*p;
			swap(f[i],f[j]); fu=-fu;
		}
		re=1ll*re*f[i][i]%mod;
	}
	return re*fu;
}

int main()
{
	scanf("%d %d",&n,&m);for(int i=1;i<=m;i++)
	scanf("%d %d %d",&e[i].x,&e[i].y,&e[i].z);sort(e+1,e+m+1,cmp);
	F.init();for(int i=1;i<=m;i++)if(F.link(e[i].x,e[i].y))v[i]=true;
	for(int i=2;i<=n;i++)if(F.findfa(i)!=F.findfa(1))return printf("0"),0; 
	int now=1;while(now<=m)
	{
		bool con=false;int to=now;for(;e[to].z==e[now].z&&to<=m;to++)con|=v[to];if(!con){now=to;continue;}
		F.init();for(int i=1;i<=m;i++)if(v[i]&&e[i].z!=e[now].z)F.link(e[i].x,e[i].y);
		int id=0;memset(belong,0,sizeof(belong));memset(f,0,sizeof(f));
		for(int i=1;i<=n;i++)if(F.fa[i]==i)belong[i]=++id;
		for(int i=1;i<=n;i++)belong[i]=belong[F.findfa(i)];
		for(int i=now;i<to;i++){
			int x=belong[e[i].x],y=belong[e[i].y];
			f[x][y]--,f[y][x]--,f[x][x]++,f[y][y]++;
		}
		ans=1ll*ans*det(id-1)%mod;now=to;
	}
	printf("%d",ans);
}

你可能感兴趣的:(题解_杂)