最小生成树个数 并查集压缩路径

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

Time Limit: 1 Sec  Memory Limit: 162 MB
Submit: 5843  Solved: 2379
[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,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

显然最小生成树有多种 ,那么关键就是在权值相同的边上了 ,那么不妨排序后用一个结构体划分权值相同的边的左右界限,而后记录其联通的个数,那么就可以去进行一次深搜了 ,对于权值相同的一组边,其边可以选择不连或者连,如果连的话要保证将要进行联通的两个点不在一个连通块中,因为树的话不能有环啊。 而后sum++的条件也比较好判断,做完这组边的最后一个,并且联通的个数等于记录的个数,就可以了。详细些的证明见文底

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int mod=31011;
int fa[108],tot=0,cnt=0,sum,ans=1;
struct Edge{int u,v,w;}e[1108];
struct Seg{int l,r,cont;}s[1108];
bool cmp(const Edge &a,const Edge &b) {return a.w<b.w;}
int findx(int x){return x==fa[x]?x:findx(fa[x]);}     //普通版
int findy(int y){return fa[y]=(y==fa[y]?y:findy(fa[y]));}   //压缩路径版
void dfs(int pos,int now,int k){
    if(now==s[pos].r+1) {
        if(k==s[pos].cont) ++sum;
        return;
    }
    int u=findx(e[now].u),v=findx(e[now].v);   //这里必须用普通版,因为压缩路径会导致回溯的时候出现父节点指向错误比如fa[1]=4,fa[2]=4,然后此时要连接3,4那么fa[4]=fa[1]=fa[2]=3 ,而回溯撤边的时候,fa[1],fa[2]的父节点本应为4,但由于压缩路径其为3.
    if(u!=v){
        fa[u]=v;
        dfs(pos,now+1,k+1);
        fa[u]=u;fa[v]=v;
    }
    dfs(pos,now+1,k);
}
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;++i) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
    for(int i=1;i<=n;++i) fa[i]=i;
    sort(e+1,e+m+1,cmp);
    for(int i=1;i<=m;++i){
        if(e[i].w!=e[i-1].w) {s[++tot].l=i;s[tot-1].r=i-1;}
        int u=findy(e[i].u),v=findy(e[i].v);
        if(u!=v) {fa[u]=v;++s[tot].cont;++cnt;}
    }
    s[tot].r=m;
    if(cnt!=n-1) {puts("0");return 0;}
    for(int i=1;i<=n;++i) fa[i]=i;
    for(int i=1;i<=tot;++i){
        sum=0;
        dfs(i,s[i].l,0);
       ans=(ans*sum)%mod;
       for(int j=s[i].l;j<=s[i].r;++j) {
        int u=findy(e[j].u),v=findy(e[j].v);
        fa[u]=v;
       }
    }
    printf("%d\n",ans);
}

思路来源 https://blog.sengxian.com/solutions/bzoj-1016    http://hzwer.com/3005.html

时间: 2024-10-12 05:57:44

最小生成树个数 并查集压缩路径的相关文章

并查集压缩路径

普通的并查集是这样婶的 void find1(int x) { int t=x; while(pre[t]!=t) { t=pre[t]; } } 如果复杂度比较高的话可以使用路径压缩(非递归版好理解,且不易爆栈),是这样婶的 void find1(int x) { int t=x; while(pre[t]!=t) { t=pre[t];//此时t就是最终的祖先 } int k=x;//k是最开始的那个点 while(t!=k) { int m=pre[k];//先保存一下结点的直接祖先: p

并查集 压缩路径

int find(int x) { int k, j, r; r = x; while(r != parent[r]) //查找跟节点 r = parent[r]; //找到跟节点,用r记录下 k = x; while(k != r) //非递归路径压缩操作 { j = parent[k]; //用j暂存parent[k]的父节点 parent[k] = r; //parent[x]指向跟节点 k = j; //k移到父节点 } return r; //返回根节点的值 } void combin

poj1703Find them, Catch them(并查集以及路径压缩)

1 /* 2 题目大意:有两个不同的黑帮,开始的时候不清楚每个人是属于哪个的! 3 执行两个操作 4 A a, b回答a, b两个人是否在同一帮派,或者不确定 5 D a, b表示a, b两个人不在同一个帮派 6 7 思路:利用并查集将相同帮派的人合并到一起! 8 a ,b 不在同一个城市,那么 a, 和mark[b]就在同一个城市, 9 b 和 mark[a]就在同一个城市! 10 */ 11 #include<iostream> 12 #include<cstring> 13

并查集的 路径压缩(递归和非递归)

这里的思路是 在每一次的找父亲节点的时候我们把每一个孩子的父亲的改成他的祖先.因为有可能一个孩子的关系很复杂可能就是一条链,这样查找就没浪费时间. //这是简单的递归实现 find (int x) { while(x!=father[x]) father[x] = find(father[x]) ; return father[x] ; } //这是非递归的 find (int x) { int r = x ; while(r != father[r])//找到r的祖先节点 r = father

SDUT 2933-人活着系列之Streetlights(最小生成树Kruskal+并查集实现)

人活着系列之Streetlights Time Limit: 1000ms   Memory limit: 65536K  有疑问?点这里^_^ 题目描述 人活着如果是为了家庭,亲情----可以说是在这个世界上最温暖人心的,也是最让人放不下的,也是我在思索这个问题最说服自己接受的答案.对,也许活着是一种责任,为了繁殖下一代,为了孝敬父母,男人要养家糊口,女人要生儿育女,就这样循环的过下去,但最终呢?还是劳苦愁烦,转眼成空呀! 为了响应政府节约能源的政策,某市要对路灯进行改革,已知该市有n个城镇,

组队赛第五场 组合隔板法+最小生成树预处理并查集

UVALive 6434 题目链接:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=4445 这题正好就是大一小学期的时候好像是曦曦出的那个牛那题吧,就是要买多少块板然后正好把牛给拦完.这题也是一样,就是找最大的距离,当做隔板,依次把最大的距离去掉,最后的就是最小的了. #include<iostream> #in

hdu 1856 并查集(路径压缩)

hdu 1856 More is better 简单的并查集,用到了路径压缩 题意:找出节点数最多的连通分支,并输出该节点数. 思路:统计每个连通分支的节点数(利用并查集构建图时进行路径压缩,用一个与根节点下标对应的sum数组记录节点数),比较后得出最大值. 1 #include<cstdio> 2 #include<cstring> 3 4 int set[10000000 + 50]; 5 int sum[10000000 + 50]; 6 bool visit[1000000

[HDOJ2818]Building Block(带权并查集,路径压缩)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2818 题意:有N个块,每次有两个操作: M x y表示把x所在的那一堆全部移到y所在的那一堆的下方. C x 询问在x下方有多少个方块. 用并查集,在路径压缩的时候后序更新当前块下有多少个其他块,注意这里“当前块下”其实和并查集是相反的,也就是父亲节点在儿子下面. 需要额外维护一个量:某一堆的块的总数,这个在unite操作的时候,如果不是同一个根,直接将一部分加到另一部分上就可以. 1 /* 2 ━

[HDOJ3635]Dragon Balls(并查集,路径压缩)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3635 题意:有n个龙珠,n个城市.初始状态第i个龙珠在第i个城市里.接下来有两个操作: T A B:把A号龙珠所在的城市的所有龙珠全部转移到B城市中. Q A:查询A龙珠,要求:A龙珠所在城市,该城市龙珠数量,A移动的次数. 思路:用并查集可以轻松解决Q的前两个问题.关键在于如何统计A的移动次数,因为在T的时候是要将A所在城市的所有龙珠都要转移到B,那就要A集合里所有龙珠的移动次数都+1.假如我们在