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

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

HINT

Source

因为相同权值的边的个数不超过10,那么我们可以发现最小生成树在某一个权值选的边的个数肯定是固定的,那么我们就可以把权值分开,然后乘法原理,枚举每条边选不选,并查集维护一下连通性。

复杂度O((m*n+m*2^10)*log m)。

之前WA了好多次主要是因为没有判图连不连通,不连通的话答案是0。

还有就是选择边的边界小错误。注意一下。

代码:

路径压缩:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <iostream>
 
const int mod = 31011;
 
int n, m;
 
struct edge {
    int u, v, w;
    bool operator < (const edge &that) const { return w < that.w; }
} e[1010]; 
 
bool cho[1010];
 
struct ufset {
    int fa[110];
    int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }
    void uni(int x, int y) {
        x = find(x), y = find(y);
        if(x == y) return;
        fa[x] = y;
    }
    void init(int n) { for(int i = 1; i <= n; i++) fa[i] = i; }
} s, ss;
 
int cnt, end, ans = 1, res;
 
void dfs(int now, int x, ufset s) {
    if(now > end) {
        if(x >= cnt) res++;
        return;
    }
    dfs(now+1, x, s);
    if(s.find(e[now].u) != s.find(e[now].v)) {
        s.uni(e[now].u, e[now].v);
        dfs(now+1, x+1, s);
    }
}
 
int main() {
    scanf("%d%d", &n, &m); s.init(n);
    for(int i = 1; i <= m; i++) scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
    std::sort(e+1, e+m+1);
    int tot = 0;
    for(int i = 1; i <= m; i++) {
        if(s.find(e[i].u) != s.find(e[i].v)) {
            s.uni(e[i].u, e[i].v);
            cho[i] = 1;
            tot++;
        }
    }
    if(tot != n-1) {
		puts("0");
		return 0;
	}
    for(int i = 1, j = 0; i <= m; i = j+1) {
        int x = 0;
        while(e[j+1].w == e[i].w) x += cho[++j];
        ss.init(n);
        for(int k = 1; k <= m; k++)
            if(cho[k] && e[k].w != e[i].w)
                ss.uni(e[k].u, e[k].v);  
        cnt = x, end = j, res = 0; 
        dfs(i, 0, ss);
        ans = ans*res%mod;
    }
    printf("%d\n", ans);
    return 0;
}

按秩合并:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <iostream>

const int mod = 31011;

int n, m;

struct edge {
	int u, v, w;
	bool operator < (const edge &that) const { return w < that.w; }
} e[1010]; 

bool cho[1010];

struct ufset {
	int fa[110], siz[110];
	int find(int x) { return x == fa[x] ? x : find(fa[x]); }
	void uni(int x, int y) {
		x = find(x), y = find(y);
		if(x == y) return;
		if(siz[x] < siz[y]) std::swap(x, y);
		fa[x] = y; siz[x] += siz[y];
	}
	void init(int n) { for(int i = 1; i <= n; i++) fa[i] = i, siz[i] = 1; }
} s, ss;

int cnt, end, ans = 1, res;

void dfs(int now, int xx) {
	if(now > end) {
		if(xx >= cnt) res++;
		return;
	}
	dfs(now+1, xx);
	if(ss.find(e[now].u) != ss.find(e[now].v)) {
		int x = ss.find(e[now].u), y = ss.find(e[now].v);
		ss.uni(x, y);
		dfs(now+1, xx+1);
		if(ss.siz[x] > ss.siz[y]) ss.siz[x] -= ss.siz[y];
		else ss.siz[y] -= ss.siz[x];
		ss.fa[x] = x, ss.fa[y] = y;
	}
}

int main() {
	scanf("%d%d", &n, &m); s.init(n);
	for(int i = 1; i <= m; i++) scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
	std::sort(e+1, e+m+1);
	int tot = 0;
	for(int i = 1; i <= m; i++) {
		if(s.find(e[i].u) != s.find(e[i].v)) {
			s.uni(e[i].u, e[i].v);
			cho[i] = 1;
			tot++;
		}
	}
	if(tot != n-1) {
		puts("0");
		return 0;
	}
	for(int i = 1, j = 0; i <= m; i = j+1) {
		int x = 0;
		while(e[j+1].w == e[i].w) x += cho[++j];
		ss.init(n);
		for(int k = 1; k <= m; k++)
			if(cho[k] && e[k].w != e[i].w)
				ss.uni(e[k].u, e[k].v);  
		cnt = x, end = j, res = 0; 
		dfs(i, 0);
		ans = ans*res%mod;
	}
	printf("%d\n", ans);
	return 0;
}

  

转载于:https://www.cnblogs.com/wazwaztime/p/7883051.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值