普通并查集练习

目录

 P1551 亲戚 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)并查集模板

P1111 修复公路 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 排序/联通块

 P1197 [JSOI2008]星球大战逆向并查集/删边/连通块计数

ZOJ3261复杂条件合并操作

 P1396 营救 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)二分答案/并查集处理


 P1551 亲戚 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)并查集模板

 模板:

#include<iostream>
using namespace std;
const int N = 1e4;
int pre[N];//每个下标节点的前驱节点
void init(int n) {//初始化
    for (int j = 0; j < n; j++) {
        pre[j] = j;
    }
}
int  find(int x) {//查找祖先
    if (pre[x] == x)return x;
    return pre[x] = find(pre[x]);
//return pre[x]==x?x:pre[x]=find(pre[x]);
}

void com(int a, int b) {//合并操作
    int t1=find(a),t2=find(b);
    if(t1==t1)
        return ;
    if(t1!=t1)//祖先不同,说明不是一个集合的,看情况进行合并
        pre[t1]=t2;
    
}
int is_sm(int x, int y) {//判断两顶点是否是一个集合的
    return find(x) == find(y);
}

int main() {
    int n, m, p;
    cin >> n >> m >> p;
    init(n);//初始化
    for (int j = 1; j <= m; j++) {
        int t1, t2;
        cin >> t1 >> t2;
        com(t1, t2);//合并顶点到同一集合
    }
    for (int j = 1; j <= p; j++) {
        int a, b;
        cin >> a >> b;
        if (is_sm(a, b) == 1)
            cout << "Yes" << endl;
        else
            cout << "No" << endl;
    }
    return 0;
}

P1111 修复公路 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 排序/联通块

思路:按路的修复时间排序,如果两顶点没有联通,就修复,合并两顶点,计数器++。修完就说明两村庄相连了。最后判断联通的村庄数量是否==n如果是说明所有村庄都是联通的,反之则不是。

#include <iostream>
#include<algorithm>
using namespace std;
const int N=1e5 + 10;
struct node{
    int a,b,t;
}per[N];
int pre[N];
 int n,m;
int fnd(int a){
    return pre[a]==a? a:pre[a]=fnd(pre[a]);
}
int cmp(node a,node b){
return a.t<b.t;
}
int main()
{
  cin>>n>>m;
  for(int j=1;j<=m;j++){
    cin>>per[j].a>>per[j].b>>per[j].t;
  }
  for(int j=1;j<=n;j++){
    pre[j]=j;
  }
    sort(per+1,per+1+m,cmp);
    for(int j=1;j<=m;j++){
        int t1=fnd(per[j].a),t2=fnd(per[j].b);//合并↓
        if(t1!=t2)pre[t1]=t2,n--;
        if(n==1){
        cout<<per[j].t<<endl;
        return 0;
        }
    }
    cout<<-1<<endl;
    return 0;

}

Python版本:

import copy

class node:
    x,y,t=0,0,0
    def __init__(self,x,y,t):
        self.x=x
        self.y=y
        self.t=t
pre=[]
def init(n):
    for i in range(n+1):
        pre.append(i)
def fnd(x):
    if pre[x]==x:
        return x
    else:
        pre[x]=fnd(pre[x])
        return pre[x]
def join(x,y):
    pre[fnd(y)]=fnd(x)
def judge(x,y):
    if fnd(x)==fnd(y):
        return True
    else:
        return False

n,m=map(int,input().split(' ',2))

l=[]
init(n)
for i in range(m):
    a,b,c=map(int,input().split())
    x=node(a,b,c)
    l.append(copy.deepcopy(x))
l=sorted(l,key=lambda x:x.t)
sum=0
ok=0
for i in range(m):
    t1=fnd(l[i].x)
    t2=fnd(l[i].y)
    if t1!=t2:
        sum=sum+1
        pre[t1]=t2
        if sum==n-1:
            print(l[i].t)
            ok=1
            break
if ok==0:
    print(-1)

 P1197 [JSOI2008]星球大战逆向并查集/删边/连通块计数

 思路:删边不可取,离线记录完所有删除的情况,用不会被处理的顶点建立初始的并查集。补顶点同时恢复边,记录连通块个数,但是因为是逆向处理,所以连通块增加时对应的答案应该是减少。

#include <iostream>
#include<ctime>
#include<math.h>
#include<string>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cstring>
#include<algorithm>
#include<limits.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;

#define f first
#define s second
#define ll long long
#define int ll
#pragma warning (disable:4996);
const int N = 4e5 + 10;
int T, n,m,Q;
int num = 0;
unordered_map<int, int>q;//hash映射会被摧毁的点
int k[N];//记录每次摧毁的点
int pre[N];//并查集祖先
pair<int, int>e[N];//记录边
vector<int>gra[N];//邻接表
deque<int>ans;//保存答案
inline void init(int n) {//inline内联后递归函数确实快很多
    for (int j = 0; j < n; j++) {
        pre[j] = j;
    }
}
inline int fnd(int a) {
    return pre[a] == a ? a : pre[a] = fnd(pre[a]);
}
inline int judge(int a, int b) {
    return fnd(a) == fnd(b);
}
inline void join(int a, int b) {
    int t1 = fnd(a), t2 = fnd(b);
    if (t1 != t2) {
        pre[t1] = t2;
        num--;
    }
}

signed main()
{ ios::sync_with_stdio(false); cout.tie(NULL);
#ifndef ONLINE_JUDGE
    //freopen("in.txt", "r", stdin);
      freopen("out.txt", "w", stdout);
#endif // !ONLINE_JUDGE
    cin >> n >> m;
   
    init(n);
    for (int j = 0; j < m; j++) {
        cin >> e[j].f >> e[j].s;

        if (e[j].first > e[j].second) {
            swap(e[j].first, e[j].second);
        }
        gra[e[j].first].push_back(e[j].second);
        gra[e[j].second].push_back(e[j].first);
    }
    cin >> Q;
    for (int j = 1; j <= Q; j++) {
        int t;
        cin >> t;
        q[t] = 1;
        k[j] = t;
    }
    num = n - Q;//摧毁完Q个点后最多的连通块个数
    for (int j = 0; j < m; j++) {
        if (q[e[j].first]==0&& q[e[j].second] == 0)//该道路不会被删
        {
            int t1 = fnd(e[j].first), t2 = fnd(e[j].second);
            if (t1 != t2) {
                pre[t1] = t2;//每合并两个顶点连通块数量++
                //但此处是逆向合并,也就是删边,连通块个数应该减少
                num--;
            }
        }
    }
    ans.push_back(num);
    for (int j = Q; j >= 1; j--) {
        q[k[j]] = 0;//恢复该点
        num++;
        for (int x : gra[k[j]]) {//链接该恢复顶点的所有边
            if (q[x] == 0) {
                join(x, k[j]);
            }
        }
        ans.push_back(num);
    }

    while (!ans.empty()) {
        cout << ans.back() << endl;
        ans.pop_back();
    }
   
    return 0;
}

 还有个类似的题,但是这题要注意编号等一些细琐但重要的条件

ZOJ3261

题意:给出n个顶点,每个顶点有对应的能量值,再给出m个顶点对表示初始图的联通情况。

最后给出Q个条件,query表示 查询某个顶点所在集合中能量最大的顶点编号(如果最大能量的顶点有相同则输出序号最小的)。destroy+顶点对表示删除该顶点之间的联通。

思路:

并查集做不到合并完然后删边,所以想到先得到查询数据然后根据删的边逆向并查集,每次删的边变为加上该边,就可以使用合并操作了。既然是逆向离线处理,所以创建初始图的时候如果该边会被删除就不要加入初始图里。

最后是合并操作,题目要求输出每个集合中能量最大的顶点,如果能量最大的有多个就输出编号小的,这个条件可以放到并查集合并操作中处理——合并祖先顶点的时候优先把能量值大的顶点当作祖先顶点,小的那个合并进去。如果能量值相同则优先编号小的当作祖先。
 

#include <iostream>
#include<ctime>
#include<math.h>
#include<string>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cstring>
#include<algorithm>
#include<limits.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
#define ll long long
#define int ll
#pragma warning (disable:4996);
const int N = 2e5 + 10;
int T, n,m,Q;
int pw[N];
int pre[N];
struct node {
    int op, first, second;
}q[N];//记录选择,离线处理,e数组记录起始的图
pair<int, int>e[N];
int fnd(int k) {
    return pre[k] == k ? k : pre[k] = fnd(pre[k]);
}
void join(int a, int b) {//满足题目规则的合并
    int t1 = fnd(a), t2 = fnd(b);
    if (t1 != t2) {
        if(pw[t1]==pw[t2])//集合中最大能量根节点相同,选择序号最小的
            pre[max(t1, t2)] = min(t1, t2);//max并入min里
        else if (pw[t1] > pw[t2]) {
            pre[t2] = t1;//能量小的并入能力大的
        }
        else {
            pre[t1] = t2;
        }
    }
}
void init(int n) {
    for (int j = 0; j < n; j++)
        pre[j] = j;
}
map<pair<int, int>, int> M;//判断顶点对是否存在
deque<int>ans;
 
signed main()
{ ios::sync_with_stdio(false); cout.tie(NULL);
#ifndef ONLINE_JUDGE
    //freopen("in.txt", "r", stdin);
  // freopen("out.txt", "w", stdout);
#endif // !ONLINE_JUDGE
    int f = 1;
    while (cin >> n) {
        M.clear();//每个样例初始化
        init(n);
        for (int j = 0; j < n; j++) {
            cin >> pw[j];
        }
        cin >> m;
        for (int j = 0; j < m; j++) {
            cin >> e[j].first >> e[j].second;
            if (e[j].first > e[j].second) swap(e[j].first, e[j].second);
            //同一边次序,编号小的在前
        }
        cin >> Q;//选择次数
        for (int j = 0; j < Q; j++) {//记录选择
            string s;
            cin >> s;
            if (s[0] == 'q') {
                q[j].op = 1;
                cin >> q[j].first;
            }
            else {
                q[j].op = 0;
                cin >> q[j].first>>q[j].second;
                if (q[j].first > q[j].second) swap(q[j].first, q[j].second);
                M[make_pair(q[j].first,q[j].second)]=1;//标记该边会被摧毁,初始建图不能加上
            }
        }
        for (int j = 0; j < m; j++) {
            if (!M[e[j]]) {//该边不会被摧毁
                join(e[j].first, e[j].second);//建立初始集合
            }
        }
        
        for (int j = Q-1; ~j; --j) {//反向查询
            if (!q[j].op) {//加边
                join(q[j].first, q[j].second);
            }
            else {
                int t = fnd(q[j].first);
                if (pw[t] > pw[q[j].first]) {//防止出现一个元素的集合
                    ans.push_back(t);
                }
                else {
                    ans.push_back(-1);
                }
            }
        }
        if (f) f = 0;//格式好烦人
        else cout << endl;
 
        while (!ans.empty()) {
            cout << ans.back() << endl;
            ans.pop_back();
        }
    } 
    return 0;
}

 P1396 营救 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)二分答案/并查集处理

 

题意:题目的问法,最大值最小的情况。很容易想到二分,而要让值最小同时肯定也必须到达目的地。所以二分答案,同时并查集合并比这个值小的所有顶点,最后判断目标起点和终点有没有联通调整二分范围。

#include <iostream>
#include<vector>
using namespace std;
const int N= 2e4  +10;
int pre[N];
int fnd(int a){
    return pre[a]==a?a:pre[a]=fnd(pre[a]);
}
void join(int a,int b){
    int t1=fnd(a),t2=fnd(b);
    if(t1!=t2)
    pre[t1]=t2;
}
int n,m,s,t;
int u[N],v[N],w[N];
int vis[N];
int gra[N][N];
int judge(int a,int b){//判断是否联通
    return fnd(a)==fnd(b);
}
int check(int k){
    for(int j=1;j<=n;j++){
        pre[j]=j;
    }
    for(int j=1;j<=m;j++){
        if(w[j]<=k){
            join(u[j],v[j]);
        }
    }
    return judge(s,t);
}
int main()
{
    cin>>n>>m>>s>>t;
    for(int j=1;j<=m;j++){
        int U,V,W;
        cin>>U>>V>>W;
        u[j]=U;
        v[j]=V;
        w[j]=W;

    }
    int l=-1,r=10001;
    int mid;
    while(l+1!=r){
      mid=(l+r)>>1;
      if(check(mid)){
          r=mid;
      }
      else
          l=mid;
    }
    cout<<r<<endl;
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值