【BZOJ1016】[JSOI2008]最小生成树计数

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

Time Limit: 1 Sec   Memory Limit: 162 MB
Submit: 1577   Solved: 601
[ Submit][ Status][ Discuss]

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,000。数据保证不会出现自回边和重边。 注意:具有相同权值的边不会超过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

Solution  最小生成树,由于相同权值的边不会很多,每次枚举相同的边中有哪些要选择即可,但要保证连通性不变。
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
#define maxn 1005
#define mod 31011
using namespace std;
struct E {int from, to, tab;}edge[maxn];
int n, m, sz, tot;
int sum[maxn], st[maxn], a[maxn], a2[maxn], vis[maxn], temp, ans = 1;
bool cmp(E a, E b) {return a.tab < b.tab;}
int fa(int x)
{
	if (a[x] == x) return x;
	a[x] = fa(a[x]);
	return a[x];
}
int fa2(int x) {if (a2[x] == x) return x; else return fa2(a2[x]);}
void Un(int x, int y)
{
	int xx = fa(x), yy = fa(y);
	if (xx != yy) a[xx] = yy;
}
void dfs(int st, int ed, int sum)
{
	if (sum == 0) {++temp; return;}
	if (st > ed) return;
	int fx = fa2(edge[st].from), fy = fa2(edge[st].to);
	if (vis[edge[st].from] && vis[edge[st].to] && fx != fy)
	{
		a2[fx] = fy;
		dfs(st + 1, ed, sum - 1);
		a2[fx] = fx;
	}
	dfs(st + 1, ed, sum);
}
void work(int x)
{
	temp = 0;
	dfs(st[x], st[x + 1] - 1, sum[x]);
	ans = (ans * temp) % mod;
}

bool kruskal()
{
	int i, j;
	sort(edge + 1, edge + m + 1, cmp);
	for (int i = 1; i <= n; i++) a[i] = a2[i] = i;
	tot = sz = 0;
	memset(vis, 0, sizeof(vis));
	for (i = 1; i <= m; i++)
	{
		if (i == 1 || edge[i].tab != edge[i - 1].tab)
		{
			st[++tot] = i, sum[tot] = 0;
			if (i != 1) work(tot - 1);
			for (j = 1; j <= n; ++j) a2[j] = a[j];
		}
		if (fa(edge[i].from) != fa(edge[i].to))
		{
			++sz;
			Un(edge[i].from, edge[i].to);
			vis[edge[i].from] = vis[edge[i].to] = 1;
			++sum[tot];
		}
		if (sz == n - 1) break;
	}
	for (j = i + 1; j <= m + 1; ++j)
		if (edge[j].tab != edge[j - 1].tab) {st[tot + 1] = j; break;}
	work(tot);
	if (sz == n - 1) return 1; return 0;
}

int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++)
		scanf("%d %d %d", &edge[i].from, &edge[i].to, &edge[i].tab);
	if (kruskal()) printf("%d\n", ans); else printf("0\n");
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值