BZOJ1016 [JSOI2008]最小生成树计数

题意:求一个n(1<=n<=100)个点,m(1<=m<=1000)条边的图的最小生成树数量,其中,相同权值的边不会超过10条。

分析:

想看怎么求生成树数量的戳这

首先我们需要知道关于MST的两个性质:

1.所有MST中,相同权值的边的出现次数相同。

2.对于某一相同权值的边,添加其出现次数次这条边并保证它达到最大效率,图的连通性不变。

所以我们需要用一遍kruskal求出每条边的出现次数,通过dfs枚举这些边的出现方法,需要注意的是dfs中并查集不能用路径压缩,因为这样回溯时没有办法修改。

#include <cstdio>
#include <cstring>
#include <algorithm>

const int N = 105, M = 1005, p = 31011;
int n,m,ans=1,tt,anss,bian,f[N];
struct nd {
	int x, y, z;
	bool operator < (const nd &rhs) const {return z < rhs.z;}
}a[M];
struct hh {int l,r,cnt;}b[M];

int fnd(int x) {return f[x] == x ? x : f[x] = fnd(f[x]);}
int fnd2(int x) {return f[x] == x ? x : fnd2(f[x]);}

void dfs(int x, int y, int z) {
	if(y == b[x].r+1) {
		if(z == b[x].cnt) anss++;
		return;
	}
	int u = fnd2(a[y].x), v = fnd2(a[y].y);
	if(u != v) {
		f[u] = v;
		dfs(x, y+1, z+1);
		f[u] = u;
	}
	dfs(x, y+1, z);
}

int main() {
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= m; i++) scanf("%d%d%d", &a[i].x, &a[i].y, &a[i].z);
	std::sort(a+1, a+1+m);
	for(int i = 1; i <= n; i++) f[i] = i;
	for(int i = 1; i <= m; i++) {
		if(a[i].z != a[i-1].z) b[tt++].r = i-1, b[tt].l = i;
		if(fnd(a[i].x) != fnd(a[i].y)) b[tt].cnt++, f[fnd(a[i].x)] = fnd(a[i].y), bian++;
	}
	b[tt].r = m;
	if(bian != n-1) {puts("0"); return 0;}
	for(int i = 1; i <= n; i++) f[i] = i;
	for(int i = 1; i <= tt; i++) {
		anss = 0;
		dfs(i, b[i].l, 0);
		ans = ans*anss%p;
		for(int j = b[i].l; j <= b[i].r; j++) if(fnd(a[j].x) != fnd(a[j].y))
			f[fnd(a[j].x)] = fnd(a[j].y);
	}
	printf("%d", ans);
	return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值