• [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; (1leq c_ileq 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::
    ");
    		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
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    论工作动力的来源是什么?答案是来自于实现自己的梦想
    向梦想者致敬
    内置函数,递归函数,模块与包,开发目录规范
    迭代器,生成器
    闭包函数,装饰器,语法糖
    函数对象,名称空间及查找,作用域
    函数调用与参数
    字符编码
    文件处理
    python 11.5数据类型及常用方法
  • 原文地址:https://www.cnblogs.com/YoungNeal/p/9264320.html
Copyright © 2020-2023  润新知