洛谷專題-並查集

P1196 [NOI2002]銀河英雄傳說(帶權並查集)node

題意:ios

有n艘艦依次排序,每次將i及其身後的艦艇合併至j及其全部艦艇以後,每次詢問i到j艦艇之間的距離,若是不在一列輸出-1數組

思路:ide

單純的合併與查詢是否在一列操做比較簡單,難的在於查詢距離spa

首先咱們須要三個數組fa[i],sum[i],dis[i]分別爲i的父親,i列全部的艦艇數,與其到其父親的距離3d

可能有人會想i到其父親的距離不都是1嘛,其實在路徑壓縮過程當中,父親會與實際的狀況不符,雖然直接相連可是可能距離並不爲1code

如今考慮合併(i,j)操做,每次合併操做只要對第一艘艦艇進行修改就行了,分別修改blog

dis數組的修改直接等於sum[j],以後將sum[j]+=sum[i],並將sum[i]=0排序

在每次查詢時都會進行路徑壓縮,所以dis[k](k爲排在i以後的艦艇)雖然在合併時沒有修改,可是會在路徑壓縮(查詢父親)時修改爲到該列第一艘艦艇的距離ci

以後在利用前綴後的思想,(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
相關文章
相關標籤/搜索