[JSOI2008] 最小生成树计数

Descriptioin

现在给出了一个简单无向加权图。你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的最小生成树。(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的)。由于不同的最小生成树可能很多,所以你只需要输出方案数对31011的模就可以了。

Input

第一行包含两个数,n和m,其中1<=n<=100; 1<=m<=1000; 表示该无向图的节点数和边数。每个节点用1~n的整数编号。

接下来的m行,每行包含两个整数:a, b, c,表示节点a, b之间的边的权值为c,其中1<=c<=1,000,000,000。

数据保证不会出现自回边和重边。注意:具有相同权值的边不会超过10条。

Output

输出不同的最小生成树有多少个。你只需要输出数量对31011的模就可以了。

Hint

说明 1<=n<=100; 1<=m<=1000; \(1\leq c_i\leq 10^9\)

Solution

求出无向图中有多少个不同的最小生成树。

有这样一个性质:这张图的所有最小生成树中,边权的大小和个数是一定的。再结合克鲁斯卡尔的做法,容易想到对于每种边权单独处理生成树个数再用乘法原理乘起来就好了。

单独处理生成树个数可以用矩阵树定理,然后就没别的什么了。

正解是辗转相减,显然我不会,还好这题 \(n\) 比较小,暴力乘也没事。

Code

#include<map>
#include<cstdio>
#include<cctype>
#include<algorithm>
#define N 105
#define db double
#define mod 31011
#define eps 1e-5
#define int long long
#define fabs(A) ((A)<0?-(A):(A))

db a[N][N];
int n,m,cnt;
int belong[N];
int father[N];
int used[N*10];
std::map<int,int> mp;
void swap(db &x,db &y){db t=x;x=y;y=t;}

struct Edge{
	int x,y,w;
	friend bool operator<(Edge a,Edge b){
		return a.w<b.w;
	}
}edge[N*10];

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

int getint(){
	int x=0;char ch=getchar();
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return x;
}

int gauss(){
	for(int i=1;i<cnt;i++){
		/*printf("first::\n");
		for(int j=1;j<cnt;j++){
			for(int p=1;p<cnt;p++)
				printf("%.2lf ",a[j][p]);
			puts("");
		}puts("");*/
		int idx=0;
		for(int j=i;j<cnt;j++){
			if(fabs(a[j][i])>eps){
				idx=j;
				break;
			}
		}
		if(idx!=i){
			for(int j=1;j<cnt;j++)
				swap(a[idx][j],a[i][j]);
		}
		for(int j=i+1;j<cnt;j++){
			db res=a[j][i]/a[i][i];
			for(int k=i;k<cnt;k++)
				a[j][k]-=res*a[i][k];
		}
	}
	db ans=1;
	for(int i=1;i<cnt;i++)
		ans*=a[i][i];
	int s=(int)(ans+eps+eps);
	return s%mod;
}

signed main(){
	n=getint(),m=getint();
	for(int i=1;i<=m;i++){
		edge[i].x=getint();
		edge[i].y=getint();
		edge[i].w=getint();
	}
	std::sort(edge+1,edge+1+m);
	for(int i=1;i<=n;i++)
		father[i]=i;
	int g=n;
	for(int i=1;i<=m and g!=1;i++){
		int x=find(edge[i].x);
		int y=find(edge[i].y);
		if(x!=y){
			g--;
			mp[edge[i].w]=1;
			used[i]=1;
			father[x]=y;
		}
	}
	if(g>1)
		return puts("0"),0;
	int ans=1;
	for(int i=1;i<=m;i++){
		if(!mp[edge[i].w])
			continue;
		for(int j=1;j<=n;j++){
			father[j]=j;
			belong[j]=0;
		} int idx=0;
		for(int j=1;j<=m;j++){
			if(!idx and edge[j].w==edge[i].w)
				idx=j;
			if(!used[j] or edge[j].w==edge[i].w)
				continue;
			int x=find(edge[j].x);
			int y=find(edge[j].y);
			if(x!=y)
				father[x]=y;
		}
		cnt=0;
		for(int k=1;k<=n;k++){
			for(int j=1;j<=n;j++)
				a[k][j]=0;
		}
		for(int j=1;j<=n;j++){
			if(!belong[find(j)])
				belong[find(j)]=++cnt;
			belong[j]=belong[find(j)];
		}
		for(int j=idx;;j++){
			if(edge[j].w!=edge[i].w){
				i=j-1;
				break;
			}
			int x=find(edge[j].x);
			int y=find(edge[j].y);
			if(x!=y){
				a[belong[x]][belong[x]]+=1;a[belong[y]][belong[y]]+=1;
				a[belong[x]][belong[y]]-=1;a[belong[y]][belong[x]]-=1;
			}
		}
		int k=gauss();
		ans=ans*k%mod;
	}
	printf("%lld\n",ans);
	return 0;
}
posted @ 2018-07-04 17:23  YoungNeal  阅读(244)  评论(0编辑  收藏  举报