首页 > 其他分享 >并查集详解

并查集详解

时间:2024-08-07 21:16:46浏览次数:14  
标签:pre ch int 查集 le read 详解 rk

并查集

并查集是一种树形数据结构,用于处理一些不相交集合的合并及查询问题(即所谓的并、查)。
具体详解见此
并查集本身是真的太板了。。普及-以下的题基本全是板。
直接见例题吧:

板子一

【模板】并查集

题目描述

如题,现在有一个并查集,你需要完成合并和查询操作。

【】输入格式】

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

接下来 \(M\) 行,每行包含三个整数 \(Z_i,X_i,Y_i\) 。

当 \(Z_i=1\) 时,将 \(X_i\) 与 \(Y_i\) 所在的集合合并。

当 \(Z_i=2\) 时,输出 \(X_i\) 与 \(Y_i\) 是否在同一集合内,是的输出
Y ;否则输出 N

【输出格式】

对于每一个 \(Z_i=2\) 的操作,都有一行输出,每行包含一个大写字母,为 Y 或者 N

【样例输入 】

4 7
2 1 2
1 1 2
2 1 2
1 3 4
2 1 4
1 2 3
2 1 4

【样例输出 】

N
Y
N
Y

【提示】

对于 \(30\%\) 的数据,\(N \le 10\),\(M \le 20\)。

对于 \(70\%\) 的数据,\(N \le 100\),\(M \le 10^3\)。

对于 \(100\%\) 的数据,\(1\le N \le 10^4\),\(1\le M \le 2\times 10^5\),\(1 \le X_i, Y_i \le N\),\(Z_i \in \{ 1, 2 \}\)。

解析

如题所见,纯板子,我连代码注释都不想写。但还是要说一下抄板子一定要看清楚,如果板子哪个for循环里面和你自身循环用法的习惯不一样可能就大寄了。(代价就是找了一天才找出来)。

code

全是板子有啥好注释的(
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
const int maxn=1e6;
int pre[maxn],rk[maxn];
inline int read()//快读
{
    int num=0,w=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') w=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        num=(num<<1)+(num<<3)+ch-'0';
        ch=getchar();
    }
    return num*w;
}
void init(int n){
    for(int i=1;i<=n;i++){
     pre[i]=i;
     rk[i]=1;
    }
}
/*int find(int x){
    if(pre[x]==x) return x;
    return find(pre[x]);
}*/
int find(int x){
     if(pre[x]==x)return x;
     return pre[x]=find(pre[x]);
}
bool join(int x,int y){
    x=find(x);
    y=find(y);
    if(x==y) return false;
    if(rk[x]>rk[y]) pre[y]=x;
    else{
        if(rk[x]==rk[y]) rk[y]++;
        pre[x]=y;
    }
    return true;
}
int n,m,z;
signed main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    n=read();m=read();
    init(n);
    while(m--){
        z=read();
        int x=read(),y=read();
        if(z==1) join(x,y);
        if(z==2){
            //cout<<find(x)<<" "<<find(y)<<endl;
            if(find(x)==find(y)) cout<<"Y"<<endl;
            else cout<<"N"<<endl;
        }
    }
}

板子二

亲戚

题目背景

若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系。

【题目描述】

规定:\(x\) 和 \(y\) 是亲戚,\(y\) 和 \(z\) 是亲戚,那么 \(x\) 和 \(z\) 也是亲戚。如果 \(x\),\(y\) 是亲戚,那么 \(x\) 的亲戚都是 \(y\) 的亲戚,\(y\) 的亲戚也都是 \(x\) 的亲戚。

【输入格式】

第一行:三个整数 \(n,m,p\),(\(n,m,p \le 5000\)),分别表示有 \(n\) 个人,\(m\) 个亲戚关系,询问 \(p\) 对亲戚关系。

以下 \(m\) 行:每行两个数 \(M_i\),\(M_j\),\(1 \le M_i,~M_j\le n\),表示 \(M_i\) 和 \(M_j\) 具有亲戚关系。

接下来 \(p\) 行:每行两个数 \(P_i,P_j\),询问 \(P_i\) 和 \(P_j\) 是否具有亲戚关系。

输出格式

\(p\) 行,每行一个 YesNo。表示第 \(i\) 个询问的答案为“具有”或“不具有”亲戚关系。

【样例输入】

6 5 3
1 2
1 5
3 4
5 2
1 3
1 4
2 3
5 6

【样例输出】

Yes
Yes
No

解析

没啥好讲的,但是一个==写成=是真能把人卡一个上午QAQ。

code

依旧没有注释
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
const int maxn=1e6;
int pre[maxn],rk[maxn];
inline int read()//快读
{
    int num=0,w=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') w=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        num=(num<<1)+(num<<3)+ch-'0';
        ch=getchar();
    }
    return num*w;
}
void init(int n){
    for(int i=0;i<n;i++){
     pre[i]=i;
     rk[i]=1;
    }
}
/*int find(int x){
    if(pre[x]==x) return x;
    return find(pre[x]);
}*/
int find(int x){
     if(pre[x]==x)return x;
     return pre[x]=find(pre[x]);
}
bool join(int x,int y){
    x=find(x);
    y=find(y);
    if(x==y) return false;
    if(rk[x]>rk[y]) pre[y]=x;
    else{
        if(rk[x]==rk[y]) rk[y]++;
        pre[x]=y;
    }
    return true;
}
int n,m,p;
signed main(){
    n=read();m=read();p=read();
    init(n);
    while(m--){
        int i=read(),j=read();
        join(i,j);
    }
    while(p--){
        int i=read(),j=read();
        if(find(i)==find(j)) cout<<"Yes"<<endl;
        else cout<<"No"<<endl;
    }
}

板子三

修复公路

题目背景

A 地区在地震过后,连接所有村庄的公路都造成了损坏而无法通车。政府派人修复这些公路。

【题目描述】

给出 A 地区的村庄数 \(N\),和公路数 \(M\),公路是双向的。并告诉你每条公路的连着哪两个村庄,并告诉你什么时候能修完这条公路。问最早什么时候任意两个村庄能够通车,即最早什么时候任意两条村庄都存在至少一条修复完成的道路(可以由多条公路连成一条道路)。

【输入格式】

第 \(1\) 行两个正整数 \(N,M\)。

下面 \(M\) 行,每行 \(3\) 个正整数 \(x,y,t\),告诉你这条公路连着 \(x,y\) 两个村庄,在时间 \(t\) 时能修复完成这条公路。

【输出格式】

如果全部公路修复完毕仍然存在两个村庄无法通车,则输出 \(-1\),否则输出最早什么时候任意两个村庄能够通车。

【样例输入】

4 4
1 2 6
1 3 4
1 4 5
4 2 3

【样例输出 】

5

【提示】

\(1\leq x, y\leq N \le 10 ^ 3\),\(1\leq M, t \le 10 ^ 5\)。

解析

板板板,不过好像也因为不知名错误卡了蛮久。

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
const int maxn=1e6;
int pre[maxn],rk[maxn];
struct node{
    int x,y,t;
}a[maxn];
bool cmp(node a1,node a2){
    return a1.t<a2.t;
}
inline int read()//快读
{
    int num=0,w=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') w=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        num=(num<<1)+(num<<3)+ch-'0';
        ch=getchar();
    }
    return num*w;
}
void init(int n){
    for(int i=1;i<=n;i++){
     pre[i]=i;
     rk[i]=1;
    }
}
/*int find(int x){
    if(pre[x]==x) return x;
    return find(pre[x]);
}*/
int find(int x){
     if(pre[x]==x)return x;
     return pre[x]=find(pre[x]);
}
bool join(int x,int y){
    x=find(x);
   // cout<<pre[x]<<" ";
    y=find(y);
    //cout<<pre[y]<<endl;
    if(x==y) return false;
    if(rk[x]>rk[y]) pre[y]=x;
    else{
        if(rk[x]==rk[y]) rk[y]++;
        pre[x]=y;
    }
    return true;
}
bool jud(int n){
    int sum=0;
     for(int i=1;i<=n;i++){
        if(find(i)==i) sum++;
        //cout<<sum<<endl;
        if(sum>1) return false;
     }
     return true;
}
int n,m;
signed main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    n=read(),m=read();
    init(n);
    for(int i=1;i<=m;i++){
        a[i].x=read();
        a[i].y=read();
        a[i].t=read();
    }
    sort(a+1,a+m+1,cmp);
    //for(int i=1;i<=m;i++) cout<<a[i].t<<" ";
    //cout<<endl;
    //for(int i=1;i<=m;i++) cout<<a[i].y<<" ";
    //cout<<endl;
    for(int i=1;i<=m;i++){
        join(a[i].x,a[i].y);
        if(jud(n)){
            cout<<a[i].t<<endl;
            return 0;
        }
        //cout<<a[i].t<<endl;
        //for(int i=1;i<=m;i++) cout<<a[i].t<<" ";
        //cout<<endl;
        //for(int i=1;i<=n;i++) cout<<pre[i]<<" ";
        //cout<<endl;
    }
    cout<<-1<<endl;
}

板子四

板子太多了,编辑也是一种折磨
朋友

题目背景

小明在 A 公司工作,小红在 B 公司工作。

【题目描述】

这两个公司的员工有一个特点:一个公司的员工都是同性。

A 公司有 \(N\) 名员工,其中有 \(P\) 对朋友关系。B 公司有 \(M\) 名员工,其中有 \(Q\) 对朋友关系。朋友的朋友一定还是朋友。

每对朋友关系用两个整数 \((X_i,Y_i)\) 组成,表示朋友的编号分别为 \(X_i,Y_i\)。男人的编号是正数,女人的编号是负数。小明的编号是 \(1\),小红的编号是 \(-1\)。

大家都知道,小明和小红是朋友,那么,请你写一个程序求出两公司之间,通过小明和小红认识的人最多一共能配成多少对情侣(包括他们自己)。

【输入格式】

输入的第一行,包含 \(4\) 个空格隔开的正整数 \(N,M,P,Q\)。

之后 \(P\) 行,每行两个正整数 \(X_i,Y_i\)。

之后 \(Q\) 行,每行两个负整数 \(X_i,Y_i\)。

【输出格式】

输出一行一个正整数,表示通过小明和小红认识的人最多一共能配成多少对情侣(包括他们自己)。

【样例输入】

4 3 4 2
1 1
1 2
2 3
1 3
-1 -2
-3 -3

【样例输出】

2

【提示】

对于 \(30 \%\) 的数据,\(N,M \le 100\),\(P,Q \le 200\);

对于 \(80 \%\) 的数据,\(N,M \le 4 \times 10^3\),\(P,Q \le 10^4\);

对于 \(100 \%\) 的数据,\(N,M \le 10^4\),\(P,Q \le 2 \times 10^4\)。

解析

此题相较上几题的不同在于需分成两个并查集,由于有负数,所以可以使用map,是并查集+stl的组合

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
const int maxn=1e6;
map<int,int>pre,rk;
int n,m,p,q,nu1,nu2;
inline int read()//快读
{
    int num=0,w=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') w=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        num=(num<<1)+(num<<3)+ch-'0';
        ch=getchar();
    }
    return num*w;
}
void init(int n,int m){
     for(int i=1;i<=n;i++){
        pre[i]=i;
        rk[i]=1;
        }
        for(int i=-1;i>=-m;i--){
            pre[i]=i;
            rk[i]=1;
        }
}
int find(int a){
    if(a==pre[a]) return a;
    return pre[a]=find(pre[a]);
}
bool join(int a,int b){
      a=find(a);b=find(b);
      if(a==b) return false;
      if(rk[a]>rk[b]) pre[b]=pre[a];
      else{
      if(rk[a]==rk[b]) rk[b]++;
        pre[a]=pre[b];
        }
        return true;
      }
signed main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    n=read();m=read();p=read();q=read();
    init(n,m);
    while(p--) join(read(),read());
    while(q--){
        join(read(),read());
        //for(int i=-1;i>=-m;i--) cout<<find(i)<<" ";
    //cout<<endl;
        }
   // for(int i=1;i<=n;i++) cout<<find(i)<<" ";
    //cout<<endl;
    for(int i=1;i<=n;i++) if(find(1)==find(i)) nu1++;
    //for(int i=-1;i>=-m;i--) cout<<find(i)<<" ";
   // cout<<endl;
   //cout<<find(-5)<<" "<<find(-1)<<endl;
    for(int i=-1;i>=-m;i--) if(find(-1)==find(i)) nu2++;
   // cout<<nu1<<" "<<nu2<<endl;
    if(nu1>nu2) cout<<nu2<<endl;
    else cout<<nu1<<endl;
}


板子五

一中校运会之百米跑

题目背景

在一大堆秀恩爱的 ** 之中,来不及秀恩爱的苏大学神踏着坚定(?)的步伐走向了 \(100\) 米跑的起点。这时苏大学神发现,百米赛跑的参赛同学实在是太多了,连体育老师也忙不过来。这时体育老师发现了身为体育委员的苏大学神,便来找他帮忙。

可是苏大学神需要热身,不然跑到一半就会抽(筋)、于是他就找到了你。。。如果你帮助体育老师解决了问题,老师就会给你 \(5\) 个积分。

【题目描述】

假设一共有 \(N\)(\(2\leq N\leq 2\times 10^4\))个参赛选手。(尼玛全校学生都没这么多吧)

老师会告诉你这 \(N\) 个选手的名字。

接着会告诉你 \(M\)(\(1\leq M\leq 10^6\))句话,即告诉你学生 A 与学生 B 在同一个组里。

如果学生 A 与学生 B 在同一组里,学生 B 与学生 C 也在同一组里,就说明学生 A 与学生 C 在同一组。

然后老师会问你 \(K\)(\(1\leq K\leq 10^6\))句话,即学生 X 和学生 Y 是否在同一组里。

若是则输出 Yes.,否则输出 No.

【输入格式】

第一行输入 \(N\) 和 \(M\)。

接下来 \(N\) 行输入每一个同学的名字。

再往下 \(M\) 行每行输入两个名字,且保证这两个名字都在上面的 \(N\) 行中出现过,表示这两个参赛选手在同一个组里。

再来输入 \(K\)。

接下来输入 \(K\) 个体育老师的询问。

【输出格式】

对于每一个体育老师的询问,输出 Yes.No.

【样例输入】

10 6
Jack
Mike
ASDA
Michel
brabrabra
HeHe
HeHE
papapa
HeY
Obama
Jack Obama
HeHe HeHE
brabrabra HeHe
Obama ASDA
papapa Obama
Obama HeHE
3
Mike Obama
HeHE Jack
papapa brabrabra

【样例输出】

No.
Yes.
Yes.

解析

同样是一道map+并查集,但是这题让我意识到快读和关闭输入输出同步流是相冲突的,特别是在变量中含string型的情况,估计是cin>>string实际上是重载成getchar或者getline从而导致string型无法正常赋值。所以最好长长记性。真有string类就别手贱加个关闭输入输出同步流了。

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
const int maxn=1e6;
map<string,string>pre;
map<string,int>rk;
inline int read()//快读
{
    int num=0,w=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') w=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        num=(num<<1)+(num<<3)+ch-'0';
        ch=getchar();
    }
    return num*w;
}
/*void init(int n){
    for(int i=1;i<=n;i++){
     pre[i]=i;
     rk[i]=1;
    }
}*/
/*int find(int x){
    if(pre[x]==x) return x;
    return find(pre[x]);
}*/
string find(string x){
     if(pre[x]==x)return x;
     return pre[x]=find(pre[x]);
}
bool issame(string x, string y)      		
{  
  //  cout<<x<<" "<<y<<endl;
    return find(x) == find(y);  	
}
bool join(string x,string y){
    x=find(x);
    y=find(y);
    if(x==y) return false;
    if(rk[x]>rk[y]) pre[y]=x;
    else{
        if(rk[x]==rk[y]) rk[y]++;
        pre[x]=y;
    }
    return true;
}
int n,m,k;
signed main(){
    //ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    string s,g;
    n=read();m=read();
    for(int i=1;i<=n;i++){
        cin>>s;
    //    cout<<s<<endl;
        pre[s]=s;
        rk[s]=1;
    }
    while(m--){
      cin>>s;
      cin>>g;
      join(s,g);
    }
    k=read();
   while(k--){
    cin>>s>>g;
    //cout<<s<<" "<<g<<endl;
    if(issame(s,g)) cout<<"Yes."<<endl;
    else cout<<"No."<<endl;
   }
}

板子五

村村通

题目描述

某市调查城镇交通状况,得到现有城镇道路统计表。表中列出了每条道路直接连通的城镇。市政府 "村村通工程" 的目标是使全市任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要相互之间可达即可)。请你计算出最少还需要建设多少条道路?

【输入格式】

输入包含若干组测试数据,每组测试数据的第一行给出两个用空格隔开的正整数,分别是城镇数目 \(n\) 和道路数目 \(m\) ;随后的 \(m\) 行对应 \(m\) 条道路,每行给出一对用空格隔开的正整数,分别是该条道路直接相连的两个城镇的编号。简单起见,城镇从 \(1\) 到 \(n\) 编号。

注意:两个城市间可以有多条道路相通。

在输入数据的最后,为一行一个整数 \(0\),代表测试数据的结尾。

【输出格式】

对于每组数据,对应一行一个整数。表示最少还需要建设的道路数目。

【样例输入】

4 2
1 3
4 3
3 3
1 2
1 3
2 3
5 2
1 2
3 5
999 0
0

【样例输出】

1
0
2
998

【数据规模与约定】

对于 \(100\%\) 的数据,保证 \(1 \le n < 1000\) 。

解析

就合并完成工作完成后再循环一遍村庄看谁还没合并就行。

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
const int maxn=1e6;
int pre[maxn],rk[maxn];
inline int read()//快读
{
    int num=0,w=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') w=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        num=(num<<1)+(num<<3)+ch-'0';
        ch=getchar();
    }
    return num*w;
}
void init(int n){
    for(int i=1;i<=n;i++){
     pre[i]=i;
     rk[i]=1;
    }
}
/*int find(int x){
    if(pre[x]==x) return x;
    return find(pre[x]);
}*/
int find(int x){
     if(pre[x]==x)return x;
     return pre[x]=find(pre[x]);
}
bool join(int x,int y){
    x=find(x);
    y=find(y);
    if(x==y) return false;
    if(rk[x]>rk[y]) pre[y]=x;
    else{
        if(rk[x]==rk[y]) rk[y]++;
        pre[x]=y;
    }
    return true;
}
int jud(int n){
    int num=0;
    for(int i=1;i<=n;i++) if(pre[i]==i) num++;
    return num-1; 
}
int n,m;
signed main(){
    while(n=read()){
        int ans=0;
        init(n);
        m=read();
        while(m--) join(read(),read());
         cout<<jud(n)<<endl;
    }
}

标签:pre,ch,int,查集,le,read,详解,rk
From: https://www.cnblogs.com/xuan2985/p/18347896

相关文章

  • xss.haozi靶场详解
    0x00直接输入即可<script>alert(1)</script>0x01正常输入发现被下面注释了只需要加个闭合即可</textarea><script>alert(1)</script>0x02这关就是闭合问题"><script>alert(1)</script>0x03正常输入发现()被过滤了将括号改为反引号即可<script>alert`1`&l......
  • Kotlin 控制流和数组操作详解
    Kotlinwhen与编写许多if..else表达式相比,您可以使用when表达式,它更易读。它用于选择要执行的多个代码块中的一个:示例使用星期几的编号来计算星期几的名称:valday=4valresult=when(day){1->"Monday"2->"Tuesday"3->"Wednesday"4->"Thursday......
  • 知识分享 | 详解整车区域控制器(ZCU)
    ​随着智能网联汽车技术的迅猛发展,整车区域控制器ZCU(ZoneControlUnit)作为汽车电子电气架构中的核心组件,其重要性日益凸显。ZCU不仅作为区域数据中心、IO中心及配电中心,在车辆动力、传感器管理、信息娱乐等方面发挥着关键作用,还通过高效的数据处理、信号控制及电力分配,为智能网......
  • 内存重叠以及memcpy和memmove函数详解
    内存重叠当我们进行内存拷贝(memcpy函数)时或者在自己实现内存拷贝函数strcpy时,如果存在目标地址在原地址的范围内就造成了内存重叠。一开始看到这个名词的时候,确实有点难以理解,经过学习,我利用以下的例子来说明内存重叠问题。首先,先介绍一下memcpy和memmove函数memcpy和mem......
  • C语言 操作符详解
    目录一、操作符的分类二、二进制和进制转换 2.1二进制转十进制 2.2二进制转八进制 2.3二进制转十六进制 三、原码、反码、补码四、移位操作符4.1左移操作符​编辑 4.2右移操作符五、位操作符按位与:&按位或:|按位异或:^按位取反:~六、逗号表达式七、操作......
  • 数据结构 - 并查集路径压缩
    ......
  • 超详细明了的C语言函数递归,望周知。(包含求n的阶乘顺序打印⼀个整数的每⼀位求第n个斐
    1.递归是什么?递归是学习C语⾔函数绕不开的⼀个话题,那什么是递归呢?递归其实是⼀种解决问题的⽅法,在C语⾔中,递归就是函数⾃⼰调⽤⾃⼰。写⼀个史上最简单的C语⾔递归代码#include<stdio.h>intmain(){printf......
  • 神经网络之卷积篇:详解边缘检测示例(Edge detection example)
    详解边缘检测示例卷积运算是卷积神经网络最基本的组成部分,使用边缘检测作为入门样例。在这个博客中,会看到卷积是如何进行运算的。在之前的博客中,说过神经网络的前几层是如何检测边缘的,然后,后面的层有可能检测到物体的部分区域,更靠后的一些层可能检测到完整的物体,这个例子中就是......
  • 4、Flink SQL 与 DataStream API 集成处理 Insert-Only 流详解
    处理Insert-Only流StreamTableEnvironment提供以下方法来从DataStream转换和转换到DataStream:fromDataStream(DataStream):将insert-only和任意类型的流转换为表,默认情况下不传播事件时间和水印。fromDataStream(DataStream,Schema):将insert-only和任意类型......
  • 深入解析:23种软件设计模式详解及其分类(创建型、结构型、行为型)附代码示例DEMO
    目录引言一、创建型模式1.简单工厂模式(SimpleFactoryPattern)2.抽象工厂模式(AbstractFactoryPattern)3.单例模式(SingletonPattern)4.建造者模式(BuilderPattern)5.原型模式(PrototypePattern)二、结构型模式1.适配器模式(AdapterPattern)2.桥接模式(BridgePatt......