并查集_模版

并查集

  并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。

  并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。常常在使用中以森林来表示。

例题:

输入格式:

第一行包含两个整数N、M,表示共有N个元素和M个操作。

接下来M行,每行包含三个整数Zi、Xi、Yi

当Zi=1时,将Xi与Yi所在的集合合并

当Zi=2时,输出Xi与Yi是否在同一集合内,是的话输出Y;否则话输出N

输出格式:

如上,对于每一个Zi=2的操作,都有一行输出,每行包含一个大写字母,为Y或者N

模版代码:

#include<cstdio>
#include<iostream>
using namespace std;
int a1,a2,a3,f[200001],n,m;

int getf(int o) {
    if (f[o]==o) return o;
    else return f[o]=getf(f[o]);
}

void make(int v, int u) {
    int t1,t2;
    t1=getf(v);
    t2=getf(u);
    if (t1!=t2) f[t2]=t1;
    return;
}
void find(int v,int u) {
    int t1,t2;
    t1=getf(v);
    t2=getf(u);
    if (t1==t2) printf("Y\n");
    else printf("N\n");
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i=1; i<=n; i++) f[i]=i;
    for (int i=1; i<=m; i++) {
        scanf("%d%d%d", &a1, &a2, &a3);
        if (a1==1) make(a2, a3);
        else find(a2, a3);
    }
    return 0;
}

主要题目类型:

1.团伙问题(搭桥问题,修路问题):求连通块

2.最小生成树(kruskal)

3.L最近公共祖先(LCA)中的Tarjan

并查集的合并过程:(路径压缩)

int  find(int x) {
    if(fa[x]!=x)  fa[x]=find(fa[x]);
    return fa[x];
     //或  return x==fa[x] ? x : fa[x]=find(fa[x]);
}

因此,并查集在查询是否在同一集合时十分快捷

if(fa[a]==fa[b]) {
    ...
}

与并查集相对的,可以有一种拆分集:

例题:

题目描述:
维护一个数据结构支持下列两个操作:
-删除某条边,表示为”D x”,即为删除第x条边
-查询两点是否属于一个集合,表示为”Q a b”,即为查询节点a与节点b是否在一个集合内,若在同一个集合内,输出”Yes”,否则输出”No”(输出不包括引号)

输入:
第一行包括三个正整数数据结构中节点的个数n,边数m,操作数q
接下来的m行每行包括两个正整数u,v,表示节点u与节点v在同一个集合里
再接下来的q行每行包括一个格式如题描述操作
输出:
对于每一个查询,输出包括一行,表示查询结果,即如果两点属于同一个集合输出”Yes”,否则输出”No”。

实际上,将删除操作存下来,反向就可以建立并查集,将答案倒序输出即可

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#define CL(X,N) memset(X, N, sizeof(X))
#define LL long long
using namespace std;
const int maxn=1e5+10;
int n, m, q;
int a[maxn], b[maxn];
int u[maxn], v[maxn], f[maxn];
char op[maxn];
bool del_state[maxn];

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

void merge(int x, int y) {
    f[_find(y)]=_find(x);
    return ;
}

int main() {
    scanf("%d%d%d", &n, &m, &q);
    for(int i=1; i<=n; i++) f[i]=i;
    for(int i=1; i<=m; i++) scanf("%d%d", a+i, b+i);
    for(int i=1; i<=q; i++) {
        char cmd[2];
        scanf("%s", cmd);
        op[i]=cmd[0];/*存操作符*/
        switch(cmd[0]) {
            case ‘D‘ : {
                scanf("%d", u+i);
                del_state[u[i]]=1;/*是否删除*/
                break;
            }
            case ‘Q‘ : {
                scanf("%d%d", u+i, v+i);
                break;
            }
            default : break;
        }
    }
    for(int i=1; i<=m; i++) if(!del_state[i]) merge(a[i], b[i]);
    /*将没被删除的边加入集合*/
    int ans[maxn], cnt=0;
    for(int i=q; i>0; i--) {
        if(op[i]==‘Q‘) {
            if(_find(u[i])==_find(v[i])) ans[cnt++]=1;
            else ans[cnt++]=0;/*正向存答案*/
        }
        else merge(a[u[i]], b[u[i]]);
    }
    for(int i=cnt-1; i>=0; i--) printf(ans[i] ? "Yes\n" : "No\n");/*倒序输出答案*/
    return 0;
}

如果有多种归属的情况,可以考虑建立多倍并查集

例题:

洛谷P2024 食物链

洛谷上 Sooke (dalao)的讲解很详细,此处就不再多说(才不是我懒得写了)这道题比较巧妙的地方在于判断相关性,建议一做。

至于Tarjan,这个算法主要还是于LCA有关,并查集只是中途用来判断两点是否是同一棵子树,不过对并查集的利用还是很巧妙的

下面给出部分Tarjan的模版代码:

void work(int fa,int son) {    //这里是Tarjan部分
    f[son]=son;
    for(int i=head[son];i;i=edge[i].net) {
        int to=edge[i].to;
        if(to==fa) continue;
        dis[to]=dis[son]+edge[i].c;    /*更新深度*/
        work(son,to);
        f[to]=son;
    }
    ...    return ;
}

由于递归过程中,work(son, to) 比 f[to]=son先执行,因此可以看做这是在树上自底向上将节点加入并查集

". . ." 部分可以是对深度或距离的更新查询,但由于查询顺序不一定于答案顺序相同(work(fa, son) 函数像先序遍历),

就需要先存再输出,不过整个算法的时间复杂度是十分优秀的(因为只查询了一遍)

对于答案顺序的具体原因这里不做更多说明,希望了解的还请浏览其他博客

(最后好像水了点)

原文地址:https://www.cnblogs.com/normaldisk/p/9282719.html

时间: 2024-11-05 19:31:29

并查集_模版的相关文章

并查集(个人模版)

并查集: 1 int find(int a) 2 { 3 int r=a; 4 while(f[r]!=r) 5 r=f[r]; 6 int i=a; 7 int j; 8 while(i!=r) 9 { 10 j=f[i]; 11 f[i]=r; 12 i=j; 13 } 14 return r; 15 } 16 int merge(int a,int b) 17 { 18 int A,B; 19 A=find(a); 20 B=find(b); 21 if(A!=B) 22 { 23 f[B

hdu 2473 Junk-Mail Filter(并查集_虚节点)

感觉有些难的题,刚开始就想到了设立虚节点,但是实现总是出错,因为每次设立了虚节点之后,无法将原节点和虚节点分开,导致虚节点根本无意义. 以上纯属废话,可以忽略…… 题意—— 给定n个点(0, 1, 2, ..., n-1),可进行两种操作:1. 将两个点合并到一个集合中; 2. 将一个点从原有集合中取出.问最后点有几个集合. 很明显的并查集,包含合并,删除操作. 但是,删除某节点的时候,需要保证这个集合中,除了被删除节点的其它节点不变,这点有些难以处理. 我们知道,并查集其实是一棵棵树,我们将树

带权并查集(个人模版)

带权并查集: 1 #include<stdio.h> 2 #include<string.h> 3 using namespace std; 4 int f[1000010]; 5 int sum[1000010]; 6 int find(int x) 7 { 8 if(x!=f[x]) 9 { 10 int pre=f[x];//pre是x的一个父节点. 11 f[x]=find(f[x]);//递归找祖先. 12 sum[x]+=sum[pre]; 13 } 14 return

Ex 5_26 变量约束是否能同时满足(并查集)_第九次作业

利用并查集进行处理,定义一个维护数组components,components[i]表示变量序号为i的变量所处的集合,首先处理相等的变量,把它们放入同一个集合中,最后再处理不相等变量,若两个不相等的变量处于同一个集合中,则不满足约束条件. 1 package org.xiu68.ch05.ex9; 2 3 public class Ex5_26 { 4 5 public static void main(String[] args) { 6 // TODO Auto-generated meth

并查集(模版)

题目描述 如题,现在有一个并查集,你需要完成合并和查询操作. 输入输出格式 输入格式: 第一行包含两个整数N.M,表示共有N个元素和M个操作. 接下来M行,每行包含三个整数Zi.Xi.Yi 当Zi=1时,将Xi与Yi所在的集合合并 当Zi=2时,输出Xi与Yi是否在同一集合内,是的话输出Y:否则话输出N 输出格式: 如上,对于每一个Zi=2的操作,都有一行输出,每行包含一个大写字母,为Y或者N 输入输出样例 输入样例#1: 复制 4 7 2 1 2 1 1 2 2 1 2 1 3 4 2 1 4

poj1182食物链_并查集_挑战程序设计竞赛例题

食物链 Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 65534   Accepted: 19321 Description 动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形.A吃B, B吃C,C吃A.  现有N个动物,以1-N编号.每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种.  有人用两种说法对这N个动物所构成的食物链关系进行描述:  第一种说法是"1 X Y",表示X和

[bzoj3037/2068]创世纪[Poi2004]SZP_树形dp_并查集_基环树

创世纪 SZP bzoj-3037/2068 Poi-2004 题目大意:给你n个物品,每个物品可以且仅可以控制一个物品.问:选取一些物品,使得对于任意的一个被选取的物品来讲,都存在一个没有被选取的物品,而且选取的个数最大. 注释:$1\le n \le 10^6$. 想法:显然,和骑士类似的,是一个基环树森林.如果A物品可以控制B物品,那就有B物品向A物品连边.对于每一个基环树,如果这个基环树是树的话显然变成树形dp入门题,暴力树形dp即可.然后对于基环树来讲,我们依然记录环上两点,分别以这两

数据结构_课程设计——并查集:检查网络

***************************************转载请注明出处:http://blog.csdn.net/lttree******************************************** 这两天做数据结构课程设计,因为以前做过ACM题,感觉还可以,不是很难呀 ~~~~并查集:检查网络~~~~ 题目要求: 给定一个计算机网络以及机器间的双向连线列表,每一条连线允许两端的计算机进行直接的文件传输,其他计算机间若存在一条连通路径,也可以进行间接的文件传

畅通工程(并查集模版题)

题意: 多组输入N,M,当N为0退出人输入,N是道路数目,M是村庄总数,随后N行,每行输入三个数两个村庄的编号,以及连接这两个村庄的费用.对每一组数据输出畅通工程的最低费用,如果不能畅通就输出“?”(不包括双引号) 这道题有两道链接: 一道是fjut的链接,另外一道是hdu的 http://www.fjutacm.com/Problem.jsp?pid=1214 http://acm.hdu.edu.cn/showproblem.php?pid=1863 思路:其实这道题就是一道排序+并查集题,