【BZOJ1016】 最小生成树计数

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

                                            Time Limit: 1 Sec  Memory Limit: 162 MB
                                                       Submit: 7244  Solved: 2991

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的模就可以了。

Sample Input

4 6
1 2 1
1 3 1
1 4 1
2 3 2
2 4 1
3 4 1

Sample Output

8

 

 解析:

       在网上搜题解一大堆矩阵树定理,然而我觉得完全不用说得这么高深啊,%hzw。

       做这道题之前,有必要知道最小生成树的性质:

       (1)不同的最小生成树中,每种权值的边出现的个数是确定的 
       (2)不同的生成树中,某一种权值的边连接完成后,形成的联通块状态是一样的 

       对于性质1,可以这样理解,加入一条边后,要使得整个图仍是最小生成树,必然会删掉与这条边权值相同的边,于是得证。

        对于性质2,我认为是显然的。。。

       于是我们可以把每种权值的处理看成是分开的好几步,然后根据乘法原理,将每一步得到的结果相乘。 解题步骤如下:

       1.做一遍最小生成树,统计每种权值的边使用的数量

       2.然后对于每一种权值的边搜索,得出每一种权值的边选择方案

       3,乘法原理

 

代码:

#include <bits/stdc++.h>
using namespace std;

const int Max=1005;
const int mod=31011;
int n,m,size,s,tot=0,ans=1;
long long sum;
int father[105];
struct shu{int x,y,len;};
shu edge[Max];
struct tree{int l,r,num,id;};
tree a[Max];

inline int get_int()
{
	int x=0,f=1;
	char c;
	for(c=getchar();(!isdigit(c))&&(c!='-');c=getchar());
	if(c=='-') {f=-1;c=getchar();}
	for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
	return x*f;
}

inline bool comp(const shu &a,const shu &b){return a.len < b.len;}
inline int getfather(int v){return father[v]==v ?  v : getfather(father[v]);}
inline void dfs(int i,int p,int num)
{
	if(p == a[i].r+1)
	{
	  if(num == a[i].num) sum++;
	  return;
	}
	int fax = getfather(edge[p].x),fay = getfather(edge[p].y);
	if(fax != fay)
	{
	  father[fay] = fax;
	  dfs(i,p+1,num+1);
	  father[fay] = fay,father[fax] = fax;
	}
	dfs(i,p+1,num);  //注意不能用else,因为这里的意思是不选择这条边,每条边都有这种情况。
}

int main()
{
	freopen("lx.in","r",stdin);

	n=get_int();
	m=get_int();
	for(int i=1;i<=n;i++) father[i] = i;
	for(int i=1;i<=m;i++) edge[i].x=get_int(),edge[i].y=get_int(),edge[i].len=get_int();

	sort(edge+1,edge+m+1,comp);
	for(int i=1;i<=m;i++)
	{
	  int fax=getfather(edge[i].x),fay=getfather(edge[i].y);
	  if(edge[i].len != edge[i-1].len) a[++tot].l=i,a[tot-1].r=i-1;
	  if(fax != fay)
	  {
	    s++;
	    father[fay] = fax;
	    a[tot].num++;
	  }
	}
	if(s != n-1) {putchar('0');return 0;}
	a[tot].r = m;

	for(int i=1;i<=n;i++) father[i] = i;
	for(int i=1;i<=tot;i++)
	{
	  sum=0;
	  dfs(i,a[i].l,0);
	  if(sum) ans=(long long)(ans * sum) % mod;
	  for(int j=a[i].l;j<=a[i].r;j++)
	  {
	    int fax = getfather(edge[j].x),fay = getfather(edge[j].y);
	   	if(fax != fay) father[fay] = fax;
	  }
	}

	printf("%d\n",ans);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值