bzoj 1016: [JSOI2008]最小生成树计数

Description

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

Input

  第一行包含两个数,n和m,其中1<=n<=100; 1<=m<=1000; 表示该无向图的节点数和边数。每个节点用1~n的整
数编号。接下来的m行,每行包含两个整数:a, b, c,表示节点a, b之间的边的权值为c,其中1<=c<=1,000,000,0
00。数据保证不会出现自回边和重边。注意:具有相同权值的边不会超过10条。

Output

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

真的是好巧妙的优化暴力题呀。

首先最暴力的方法肯定是2的n次方枚举每条边选或不选跑最小生成树,看一看是否与全图的最小生成树答案相等,当然一看这就不是正解,不过正解有时就是从暴力入手的。

再仔细读下题目,具有相同权值的边不会超过10条,这句话有什么意思呢,忽然就能发现一个显而易见的结论,对于任何一种最小生成树来说,每种权值边的出现次数是一样的,毕竟都是n-1条边,如果有出现权值大小次数不一样了,就不是最小生成树了。

那么有上面的结论,我们就可以想到到达每个权值的时候,只需最大2的十次方来枚举每条边用或不用啦,最后的答案就是每种边的答案相乘了,乘法原理呢。注意了,每种边用或不用不能直接用组合数来算,因为我们每个边用或不用还有一个限制条件就是边的两端不属于同一集合,需要在dfs种判断。

接着还有问题就是加每种权值边的顺序不一样会不会影响答案呢,答案是不会的。如果随着加入的边不同能够产生不同的效果,那么必然至少有一条边能够产生不同效果,那么在最小生成树的计算中就计算在内了,所以就不会产生不同的效果啦。所以我们直接顺序枚举加边即可,当一种权值的边用完之后,就把它们如最小生成树一样合并即可。

下附AC代码。

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define maxn 1005
using namespace std;
int mod=31011;
int n,m;
int fa[maxn];
int find(int now)
{
	return fa[now]==now?now:find(fa[now]);
}
int u[maxn*10],e[maxn*10],w[maxn*10],temp[maxn*10];
int cmp(int i,int j)
{
	return w[i]<w[j];
}
struct nod
{
	int l,r,cnt;
}a[maxn];
int cnt=0;
int sum=0;
int ans=1;
void dfs(int pos,int now,int loc)
{
	if(now==a[pos].r+1)
	{
		if(loc==a[pos].cnt)
		sum++;
		return;
	}
	int x=find(u[temp[now]]);
	int y=find(e[temp[now]]);
	if(x!=y)
	{
		fa[x]=y;
		dfs(pos,now+1,loc+1);
		fa[x]=x;
		fa[y]=y;
	}
	dfs(pos,now+1,loc);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&u[i],&e[i],&w[i]);
		temp[i]=i;
	}
	for(int i=1;i<=n;i++)
	fa[i]=i;
	sort(temp+1,temp+1+m,cmp);
	int tot=0;
	for(int i=1;i<=m;i++)
	{
		int now=temp[i];
		if(w[now]!=w[temp[i-1]])
		{
			a[++cnt].l=i;
			a[cnt-1].r=i-1;
		}
		int x=find(u[now]);
		int y=find(e[now]);
		if(x!=y)
		{
			a[cnt].cnt++;
			fa[x]=y;
			tot++;
		}
	}
	a[cnt].r=m;
	if(tot<n-1)
	{
		printf("0\n");
		return 0;
	}
	
	for(int i=1;i<=n;i++)
	fa[i]=i;
	
	for(int i=1;i<=cnt;i++)
	{
		sum=0;
		dfs(i,a[i].l,0);
		ans=(ans*sum)%mod;
		for(int j=a[i].l;j<=a[i].r;j++)
        {
            int x=find(u[temp[j]]),y=find(e[temp[j]]);
            if(x!=y)
			fa[x]=y;
        }
	}
	printf("%d\n",ans);
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值