洛谷专题-并查集

P1196 [NOI2002]银河英雄传说(带权并查集)

题意:

有n艘舰依次排序,每次将i及其身后的舰艇合并至j及其所有舰艇之后,每次询问i到j舰艇之间的距离,如果不在一列输出-1

思路:

单纯的合并与查询是否在一列操作比较简单,难的在于查询距离

首先我们需要三个数组fa[i],sum[i],dis[i]分别为i的父亲,i列所有的舰艇数,与其到其父亲的距离

可能有人会想i到其父亲的距离不都是1嘛,其实在路径压缩过程中,父亲会与实际的情况不符,虽然直接相连但是可能距离并不为1

现在考虑合并(i,j)操作,每次合并操作只要对第一艘舰艇进行修改就好了,分别修改

dis数组的修改直接等于sum[j],之后将sum[j]+=sum[i],并将sum[i]=0

在每次查询时都会进行路径压缩,因此dis[k](k为排在i之后的舰艇)虽然在合并时没有修改,但是会在路径压缩(查询父亲)时修改成到该列第一艘舰艇的距离

之后在利用前缀后的思想,(dis[i]-dis[j])-1即为连个舰艇之间的舰艇数了

 

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
 using namespace std;
 const int maxn=3e4+10;
 int fa[maxn],sum[maxn],dis[maxn];
 int find(int x)
 {
     if(fa[x]==x)    return x;
     int f1=fa[x],f2=find(fa[x]);
     dis[x]+=dis[f1];
     fa[x]=f2;
     return f2;
 }
 void uni(int x,int y)
 {
     int f1=find(x),f2=find(y);
     fa[f2]=f1;
     dis[f2]=sum[f1];
     sum[f1]+=sum[f2];
     sum[f2]=0;
 }
 int main()
 {
     int t,i,j;
     char op;
     scanf("%d",&t);
     for(int i=1;i<=maxn;i++){
         fa[i]=i;
         sum[i]=1;
     }
     while(t--){
         cin>>op>>i>>j;
         if(op=='M') uni(j,i);
         else{
             if(find(i)!=find(j))    cout<<-1<<endl;
             else{
                 cout<<abs(dis[j]-dis[i])-1<<endl;
             }
         }
     }
    return 0;
 }
View Code

 

P2024 [NOI2001]食物链(种类并查集)

题意:

现在有三种生物,ABC,A吃B,B吃C,C吃A,现在依次告诉你一些关系,请说出这些关系中假话的个数(假话的定义为与之前的话矛盾或不符合事实例如A吃A)

思路:

对于一对关系,比较难处理的是虽然你知道X吃Y,但是你不知道X跟Y究竟属于什么物种

那么我们可以建立3*N大小的并查集,分为A,B,C三个部分,代表着A中的生物吃B中的生物等等类推

对于一个关系比如X跟Y一类,我们在三个集合中分别将二者相连

对于关系X吃Y,我们就将A中的X与B中的Y相连,还有两个集合中操作类似

对于每个关系,我们就可以通过判断在一个集合内是否相连,或者在另一个集合内相连来判断正误了

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
 using namespace std;
 const int maxn=2e5+10;
 int fa[maxn];
 int find(int x){return fa[x]==x?x:(fa[x]=find(fa[x]));}
 int main()
 {
     int n,k,ans=0,op,x,y;
     scanf("%d%d",&n,&k);
     for(int i=1;i<=3*n;i++) fa[i]=i;
     for(int i=1;i<=k;i++){
         scanf("%d%d%d",&op,&x,&y);
         if(x>n||y>n){
             ans++;
             continue;
         }
        if(op==1){
            if(find(x)==find(y+n)||find(y)==find(x+n))    ans++;
            else{
                fa[find(x)]=find(y);
                fa[find(x+n)]=find(y+n);
                fa[find(x+2*n)]=find(y+2*n);
            }
        }
        else{
            if(x==y){ans++;continue;}
            if(find(x)==find(y)||find(x)==find(y+2*n)) ans++;
            else{
                fa[find(x)]=find(y+n);
                fa[find(x+n)]=find(y+2*n);
                fa[find(x+2*n)]=find(y);
            }
        }
     }
    cout<<ans<<endl;
    return 0;
 }
 
View Code

P1197 [JSOI2008]星球大战(逆向思维,并查集)

题意:

给你一个无向图,每次从图中删去一个点,询问每次删点过后图中连通块的数量

思路:

本题可以离线,因此我们采用离线的逆向做法

怎么个逆向呢?我们假设一开始只有所有删点操作之后的点,并算出连通块个数

之后每次向图中加入被删除的点,并统计连通块个数

如果重新对所有点跑一遍的话时间复杂度上一定会炸,对于新加入的点,我们先对当前连通块个数加1,如果遍历该点连接的所有点,如果能够合并,那么就将连通块个数减1

最后把答案倒序输出就好了

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
#include<cstring>
 using namespace std;
 const int maxn=4e5+1000;
 int flag[maxn],fa[maxn];
 vector<int>a[maxn];
 stack<int> s,q;
 int find(int x){return fa[x]==x?x:(fa[x]=find(fa[x]));}
 int main()
 {
     int n,m,k,u,v;
     memset(flag,1,sizeof(flag));
     scanf("%d%d",&n,&m);
     for(int i=1;i<=m;i++){
         scanf("%d%d",&u,&v);
         a[u].push_back(v);
         a[v].push_back(u);
     }
    scanf("%d",&k);
    for(int i=1;i<=k;i++){
        scanf("%d",&u);
        s.push(u);
        flag[u]=0;
    }
    int cnt=n-k; 
    for(int i=0;i<n;i++) fa[i]=i;
    for(int i=0;i<n;i++){
        if(!flag[i])    continue;
        else{
            for(int j=0;j<a[i].size();j++){
                if(flag[a[i][j]]){
                    int f1=find(a[i][j]),f2=find(i);
                    if(f1!=f2)    fa[f1]=find(fa[f2]),cnt--;
                }
            }
        }
    }
    q.push(cnt);
    for(int i=1;i<=k;i++){
        cnt++;
        int x=s.top();
        s.pop();
        for(int j=0;j<a[x].size();j++){
            if(flag[a[x][j]]){
                int f1=find(a[x][j]),f2=find(x);
                if(f1!=f2)    fa[f1]=find(fa[f2]),cnt--;
            }
        }
        flag[x]=1;
        q.push(cnt);
    }
    while(!q.empty()){
        cout<<q.top()<<endl;
        q.pop();
    }
    return 0;
 }
View Code

P1111 修复公路(并查集)

题意:

给你n个点,m条无向边,每条边建好都有一个时间,问什么时候各个点能互相可达

思路:

将每条边按时间排序,每次加入一条边,看边连接的两点是否在一个连通块内,不在的话合并连通块,看是否总连通块个数为1即可

 

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
 using namespace std;
 const int maxn1=1e5+10;
 const int maxn2=1e3+10;
 int fa[maxn2],siz[maxn2],n,m;
 struct node{
     int u,v,t;
 }edge[maxn1];
 int cmp(node a,node b){return a.t<b.t;}
 int find(int x){return fa[x]==x?x:(fa[x]=find(fa[x]));}
 int main()
 {
     scanf("%d%d",&n,&m);
     for(int i=0;i<m;i++) scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].t);
     sort(edge,edge+m,cmp);
    for(int i=1;i<=n;i++)    fa[i]=i;
    memset(siz,0,sizeof(siz));
    int cnt=n;
    for(int i=0;i<m;i++){
        int u=edge[i].u,v=edge[i].v,t=edge[i].t;
        int f1=find(u),f2=find(v);
        if(f1!=f2)    fa[f1]=f2,cnt--;
        if(cnt==1){
            cout<<t<<endl;
            return 0;
         }    
    }
    cout<<-1<<endl;
    return 0;
 }
View Code

 

posted @ 2020-02-14 23:53  overrate_wsj  阅读(450)  评论(0编辑  收藏  举报