【JSOI 2008】 最小生成树计数

【题目链接】

           点击打开链接

【算法】

          笔者做这题参考了这篇博客 :

          https://blog.sengxian.com/solutions/bzoj-1016

          推荐阅读

          首先,我们需要知道三个定理 :

          定理1 : 若A,B是两棵不同的最小生成树,它们的权值从小到大排列分别为 :

                        W(a1),W(a2),W(a3)....W(an-1)

                        W(b1),W(b2),W(b3)....W(bn-1)

                        那么,对于任意的i,都有W(ai) = W(bi)

          定理2 : 当最小生成树中所有w <= w0的边被加入后,图的联通性唯一

          定理3 : 若A是一棵最小生成树,A中权值为v的边有k条,那么,用任意k条权值为v的边替换A中权值为v的边且不产生

                        环的方案都是一棵最小生成树

         证明详见笔者推荐的那篇博客

         有了这三个定理,这题就很好做啦! 首先,任意求一棵最小生成树,记录每种权值的边出现的次数,然后,对每种

          权值的边做一遍深度优先搜索DFS,求出方案数,然后乘法原理,即可

【代码】

            注意因为进行DFS时需要回溯,所以,并查集不能路径压缩

#include<bits/stdc++.h>
using namespace std;
#define MAXN 110
#define MAXM 1010
const int MOD = 31011;

int n,m,i,j,ans = 1,pos,len,sum,sx,sy;
int fa[MAXN],s[MAXN],val[MAXN],l[MAXN],r[MAXN];

struct Edge
{
		int u,v,w;
} e[MAXM];
inline void init(int n)
{
		int i;
		for (i = 1; i <= n; i++) fa[i] = i;
}
int get_root(int x)
{
		if (fa[x] == x) return x;
		return get_root(fa[x]);
}	

bool cmp(Edge a,Edge b)
{
		return a.w < b.w;
}
bool kruskal()
{
		int i,cnt = 0,sx,sy;
		for (i = 1; i <= m; i++)
		{
				sx = get_root(e[i].u);
				sy = get_root(e[i].v);
				if (e[i].w == val[len]) r[len]++;
				if (sx != sy)
				{
						fa[sx] = sy;
						cnt++;
						if (e[i].w == val[len]) s[len]++;
						else
						{
								len++;
								l[len] = r[len] = i;
								s[len]++;
								val[len] = e[i].w;
						}
				}
		}			
		return cnt == n - 1;	
}
inline void dfs(int now,int r,int c)
{
		int sx,sy;
		if (now > r)
		{
				if (c == s[pos]) sum++;
				return;
		}
		dfs(now+1,r,c);
		sx = get_root(e[now].u);
		sy = get_root(e[now].v);
		if (sx != sy) 
		{
				fa[sx] = sy;
				dfs(now+1,r,c+1);
				fa[sx] = sx;
		}
}

int main() {
		
		scanf("%d%d",&n,&m);
		for (i = 1; i <= m; i++) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
		sort(e+1,e+m+1,cmp);
		init(n);
		if (!kruskal())
		{
				puts("0");
				return 0;
		}
		init(n);
		for (i = 1; i <= len; i++)
		{
				sum = 0;
				pos = i;
				dfs(l[i],r[i],0);
				ans = (ans * sum) % MOD;
				for (j = l[i]; j <= r[i]; j++)
				{
						sx = get_root(e[j].u);
						sy = get_root(e[j].v);
						if (sx != sy) fa[sx] = sy;		
				}					
		}
		printf("%d\n",ans);
		
		return 0;
	
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值