BZOJ1016 || 洛谷P4208 [JSOI2008]最小生成树计数【矩阵树定理】

时空限制 1000ms / 128MB

题目描述

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

输入格式:

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

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

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

输出格式:

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

说明

说明 1<=n<=100; 1<=m<=1000; 1 ≤ c i ≤ 1 0 9 1\leq c_i\leq 10^9 1ci109


题目分析

首先最小生成树有这样的性质

若A,B为图G的不同最小生成树
设它们的边从小到大依次为 w a 1 , w a 2 … w a n − 1 w_{a_1},w_{a_2}\dots w_{a_{n-1}} wa1,wa2wan1 w b 1 , w b 2 … w b n − 1 w_{b_1},w_{b_2}\dots w_{b_{n-1}} wb1,wb2wbn1
这些边的条数分别为 k a 1 , k a 2 … k a n − 1 k_{a_1},k_{a_2}\dots k_{a_{n-1}} ka1,ka2kan1 k b 1 , k b 2 … k b n − 1 k_{b_1},k_{b_2}\dots k_{b_{n-1}} kb1,kb2kbn1
那么有 w a i = w b i w_{a_i}=w_{b_i} wai=wbi k a i = k b i k_{a_i}=k_{b_i} kai=kbi

即所有最小生成树的边权出现情况都相同

假设当前已有一个最小生成树,若删去其中所有边权为 w i w_i wi的边,图会变成若干个森林
根据上述性质,此时在原图中所有权值为 w i w_i wi的边中取 k i k_i ki条,若能组成生成树,则一定是最小的
将此时的合法选择方案数记为 c i c_i ci,那么根据乘法原理答案 ∏ c i \prod c_i ci

所以对于本题,先用Kruskal求出原图的一个最小生成树,记录所有树边和出现过的权值
遍历每个不同权值 w i w_i wi,连接树边中所有权值不为 w i w_i wi的边,将每个联通快缩点
用原图中所有权值为 w i w_i wi的边构造基尔霍夫矩阵,应用矩阵树定理求出 c i c_i ci
乘法原理累乘 c i c_i ci即可

据说这题搜索也能过???

#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long lt;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

const int mod=31011;
const int maxn=210;
int n,m;
struct node{int u,v,dis;}E[maxn*10],T[maxn];
int fa[maxn],rem[maxn],tot;
int col[maxn],coln;
int a[maxn][maxn];

bool cmp(node a,node b){ return a.dis<b.dis;}

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

int kruskal()
{
	sort(E+1,E+1+m,cmp); int cnt=0;
	for(int i=1;i<=n;++i) fa[i]=i;
	
	for(int i=1;i<=m;++i)
	{
		int u=E[i].u,v=E[i].v;
		int fu=find(E[i].u),fv=find(E[i].v);
		if(fu!=fv)
		{
			fa[fu]=fv; T[++cnt]=E[i];
			if(E[i].dis!=rem[tot]) rem[++tot]=E[i].dis;
			if(cnt==n-1) return 1;
		}
	}
	return 0;
}

void addE(int val)
{
	for(int i=1;i<=n;++i) fa[i]=i;
	for(int i=1;i<n;++i)
	if(T[i].dis!=val)
	{
		int fu=find(T[i].u),fv=find(T[i].v);
		if(fu!=fv) fa[fu]=fv;
	}
}

void qblock()
{
	coln=0;
	for(int i=1;i<=n;++i)
	if(find(i)==i) col[i]=++coln;
	for(int i=1;i<=n;++i)
	col[i]=col[find(i)];
}

void build(int val)
{
	memset(a,0,sizeof(a));
	for(int i=1;i<=m;++i)
	if(E[i].dis==val){
		int u=col[E[i].u],v=col[E[i].v];
		a[u][u]++; a[v][v]++; a[u][v]--; a[v][u]--;
	}
}

int gauss(int lim)
{
	int res=1;
	for(int i=1;i<lim;++i)
	{
		for(int j=i+1;j<lim;++j)
		while(a[j][i])
		{
			int t=a[i][i]/a[j][i];
			for(int k=i;k<lim;++k)
			a[i][k]=(a[i][k]-t*a[j][k]+mod)%mod;
			
			swap(a[j],a[i]);
			res=-res;
		}
		res=(res*a[i][i])%mod;
	}
	return (res+mod)%mod;
}

int main() 
{ 	
	n=read();m=read();
	for(int i=1;i<=m;++i)
	E[i].u=read(),E[i].v=read(),E[i].dis=read();
	
	if(!kruskal()){ printf("0"); return 0;}
	
	int ans=1;
	for(int i=1;i<=tot;++i)
	{
		addE(rem[i]); qblock(); build(rem[i]);
		ans=ans*gauss(coln)%mod;
	}
	
	printf("%d",(ans+mod)%mod);
	return 0;
} 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值