1. 程式人生 > >並查集:學習總結

並查集:學習總結

剔除 最小值 cst 之間 int 勿噴 決戰 建立 食物

$ ??????????????? ? $學習總結:並查集

蒟蒻的第一篇博客,如有bug,請大佬提出,勿噴。

並查集:

並查集雖說是集合,不過個人覺得類似樹形結構,像森林,剛開始每一個節點是一個森林,不斷把森林合並,形成樹。

? 是一組不相交的集合。即集合之間沒有公共元素,它們的交集是空集。
? 因此沒有“求集合的交集”和“求集合的差集” 操作。
? 至於“求集合的並集”運算,則只需簡單地將兩個集合的元素合並在一起就行了,不用考慮剔除重復元素的問題。


上模板:

【 P3367 【模板】並查集】

題目描述

如題,現在有一個並查集,你需要完成合並和查詢操作。

輸入輸出格式

輸入格式:

第一行包含兩個整數N、M,表示共有N個元素和M個操作。

接下來M行,每行包含三個整數Zi、Xi、Yi

當Zi=1時,將Xi與Yi所在的集合合並

當Zi=2時,輸出Xi與Yi是否在同一集合內,是的話輸出Y;否則話輸出N

輸出格式:

如上,對於每一個Zi=2的操作,都有一行輸出,每行包含一個大寫字母,為Y或者N

代碼:

#include<iostream>
using namespace std;
int father[100000+10];
void init(){                        //初始化
    for(int i=0;i<10000+10;i++)  father[i]=i;   //爸爸就是自己
}
int find(int x){                    //找祖先
    if(father[x]==x)  return x;     //如果爸爸是自己的話,那麽他就是這整個集合的root,及祖先。
    else  return father[x]=find(father[x]); //否則繼續向上找
}
void findans(int x,int y){          //判兩數是否是否在同一集合(並查集)
    int i=find(x);                  //找祖先
    int j=find(y);                  //同上
    if(i==j)  cout<<'Y'<<endl;      //如果祖先相同,及在同一並查集
    else
    cout<<'N'<<endl;
}
void union_(int x,int y){           //合並
    int i=find(x);
    int j=find(y);
    if(i!=j)  father[i]=j;          //將一個數的父親指向另一個數
}
int main(){
    int n,m,p;
    cin>>n>>m;
    init();
    for(int i=1;i<=m;i++){
        int e,a,b;
        cin>>e>>a>>b;
        if(e==1)                    //操作:合並
        union_(a,b);
        else                        //操作:詢問
        findans(a,b);
    }
    return 0;
}

帶秩並查集:

什麽是秩:秩就是並查集的節點數。

經典題:【打擊犯罪】

某個地區有n(n≤1000)個犯罪團夥,當地警方按照他們的危險程度由高到低給他們編號為1-n,他們有些團夥之間有直接聯系,但是任意兩個團夥都可以通過直接或間接的方式聯系,這樣這裏就形成了一個龐大的犯罪集團,犯罪集團的危險程度由集團內的犯罪團夥數量唯一確定,而與單個犯罪團夥的危險程度無關(該犯罪集團的危險程度為n)。現在當地警方希望花盡量少的時間(即打擊掉盡量少的團夥),使得龐大的犯罪集團分離成若幹個較小的集團,並且他們中最大的一個的危險程度不超過n/2。為達到最好的效果,他們將按順序打擊掉編號1到k的犯罪團夥,請編程求出k的最小值。

【輸入】
第一行一個正整數n。接下來的n行每行有若幹個正整數,第一個整數表示該行除第一個外還有多少個整數,若第i行存在正整數k,表示i,k兩個團夥可以直接聯系。

【輸出】
一個正整數,為k的最小值。

【輸入樣例】
7
2 2 5
3 1 3 4
2 2 4
2 2 3
3 1 6 7
2 5 7
2 5 6

【輸出樣例】
1

【思路】:審題,題目要求,按順序打擊1~k的罪犯團夥,也就是說,要打k號犯罪團夥,必須先打掉1~k-1號犯罪團夥。所以,我們可以從後往前建並查集,一旦發現並查集的秩超過了題目要求(n/2)那麽直接輸出結果(當前操作的編號)。

【代碼】

#include<iostream>
#include<cmath>
#include<cstdlib>
#include<cstdio>
using namespace std;
int father[100000+10],num[100000+10],dis[100000+10],ans,k,o,e[1000001],a[1001][1001];
void init()     //初始化
{
    for(int i=0;i<50000+10;i++){father[i]=i;num[i]=1;}
}
int find(int x) //找祖先
{
    if(x!=father[x])
    {
        father[x]=find(father[x]);
    }
    return father[x];
}
void merge(int x,int y)
{
    int a1=find(x),a2=find(y);
    if(a1!=a2)
    {
        father[a1]=a2;
        num[a2]+=num[a1];       //秩累加
        if(num[a2]>k){cout<<o<<endl;exit(0);}   //判斷是否滿足題意
        //if(num[a2]>=k){cout<<o<<endl;exit(0);}
//        num[a1]=num[a2];
    }
}
int main()
{
    int n,m,p;
    cin>>m;
    k=m/2;
    init();
    for(int i=1;i<=m;i++)
    {
        cin>>e[i];
        for(int j=1;j<=e[i];j++)
        {
            cin>>a[i][j];
        }
    }
    for(int i=m;i>=1;i--)
    {
        o=i;
        for(int j=1;j<=e[i];j++)
        {
            if(i<a[i][j])   //如果(i>a[i][j])  a[i][j]已經被打掉了,所以沒用
            merge(i,a[i][j]);   //建立並查集
        }
    }
    return 0;
}

帶權並查集&&n倍空間

什麽是權:權就是權值……

經典題:【食物鏈】&&【銀河英雄傳說】

P1196 [NOI2002]銀河英雄傳說

題目描述

公元五八○一年,地球居民遷移至金牛座α第二行星,在那裏發表銀河聯邦創立宣言,同年改元為宇宙歷元年,並開始向銀河系深處拓展。

宇宙歷七九九年,銀河系的兩大軍事集團在巴米利恩星域爆發戰爭。泰山壓頂集團派宇宙艦隊司令萊因哈特率領十萬余艘戰艦出征,氣吞山河集團點名將楊威利組織麾下三萬艘戰艦迎敵。

楊威利擅長排兵布陣,巧妙運用各種戰術屢次以少勝多,難免恣生驕氣。在這次決戰中,他將巴米利恩星域戰場劃分成30000列,每列依次編號為1, 2, …, 30000。之後,他把自己的戰艦也依次編號為1, 2, …, 30000,讓第i號戰艦處於第i列(i = 1, 2, …, 30000),形成“一字長蛇陣”,誘敵深入。這是初始陣形。當進犯之敵到達時,楊威利會多次發布合並指令,將大部分戰艦集中在某幾列上,實施密集攻擊。合並指令為M i j,含義為讓第i號戰艦所在的整個戰艦隊列,作為一個整體(頭在前尾在後)接至第j號戰艦所在的戰艦隊列的尾部。顯然戰艦隊列是由處於同一列的一個或多個戰艦組成的。合並指令的執行結果會使隊列增大。

然而,老謀深算的萊因哈特早已在戰略上取得了主動。在交戰中,他可以通過龐大的情報網絡隨時監聽楊威利的艦隊調動指令。

在楊威利發布指令調動艦隊的同時,萊因哈特為了及時了解當前楊威利的戰艦分布情況,也會發出一些詢問指令:C i j。該指令意思是,詢問電腦,楊威利的第i號戰艦與第j號戰艦當前是否在同一列中,如果在同一列中,那麽它們之間布置有多少戰艦。

作為一個資深的高級程序設計員,你被要求編寫程序分析楊威利的指令,以及回答萊因哈特的詢問。

最終的決戰已經展開,銀河的歷史又翻過了一頁……

【樣例輸入】 
4 
M 2 3 
C 1 2 
M 2 4 
C 4 2 

【樣例輸出】 
-1 
1

說明

【樣例說明】

戰艦位置圖:表格中阿拉伯數字表示戰艦編號

【思路】

在建並查集的過程中儲存兩戰艦的距離,找答案的時候加以處理。

【代碼】

#include<iostream>
#include<cmath>
#include<cstdio>
using namespace std;
int father[100000+10],num[100000+10],dis[100000+10],ans;
void init()         //初始化
{
    for(int i=0;i<50000+10;i++){father[i]=i;num[i]=1;}
}
int find(int x)     //找祖先
{
    if(x!=father[x])
    {
        int k=father[x];
        father[x]=find(father[x]);
        dis[x]+=dis[k]; //累加兩戰艦的距離
        num[x]=num[father[x]];  //加權
    }
    return father[x];
}
int findans(int a,int b)        //找答案
{
    int r1=find(a),r2=find(b);
    if(r1!=r2)
    {
        return -1;
    }
    else{
        return abs(dis[a]-dis[b])-1;
    }
}
void merge(int x,int y) //模板
{
    int a1=find(x),a2=find(y);
    if(a1!=a2)
    {
        father[a1]=a2;
        dis[a1]=dis[a2]+num[a2];
        num[a2]+=num[a1];   //權
        num[a1]=num[a2];
    }
}
int main(){
    int n,m,p;
    cin>>m;
    init();
    for(int i=1;i<=m;i++)
    {
        int a,b;
        char e;
        cin>>e>>a>>b;
        if(e=='M')
        merge(a,b); //建並查集
        else
        {
            ans=findans(a,b);
            printf("%d\n",ans);
        }
    }
    return 0;
}

n倍空間並查集

【團夥】此題是雙倍空間,【食物鏈】是三倍空間,當然【食物鏈】也可以用帶權並查集來做。

【食物鏈】

描述

動物王國中有三類動物A,B,C,這三類動物的食物鏈構成了有趣的環形。A吃B, B吃C,C吃A。

現有N個動物,以1-N編號。每個動物都是A,B,C中的一種,但是我們並不知道它到底是哪一種。

有人用兩種說法對這N個動物所構成的食物鏈關系進行描述:

第一種說法是"1 X Y",表示X和Y是同類。

第二種說法是"2 X Y",表示X吃Y。

此人對N個動物,用上述兩種說法,一句接一句地說出K句話,這K句話有的是真的,有的是假的。當一句話滿足下列三條之一時,這句話就是假話,否則就是真話。

1) 當前的話與前面的某些真的話沖突,就是假話;

2) 當前的話中X或Y比N大,就是假話;

3) 當前的話表示X吃X,就是假話。

你的任務是根據給定的N(1 <= N <= 50,000)和K句話(0 <= K <= 100,000),輸出假話的總數。

輸入

第一行是兩個整數N和K,以一個空格分隔。

以下K行每行是三個正整數 D,X,Y,兩數之間用一個空格隔開,其中D表示說法的種類。

若D=1,則表示X和Y是同類。

若D=2,則表示X吃Y。

輸出

只有一個整數,表示假話的數目。

樣例輸入 
100 7 
1 101 1 
2 1 2 
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5 
樣例輸出 
3

【思路】
開三倍空間,指的是把數組開三倍空間,分別存同類,獵物,天敵。然後把每一次輸入的數據進行判斷,如果發現不符題意,那麽就是謊話,否則把所有有關聯的同類連接在一起。

【代碼】

#include<iostream>
#include<string.h>
#include<cstdio>
#include<cstdlib>
using namespace std;
long long father[150000+10],t[1000001],ans,o,x,y,k,vis[100001],a,b,c;
void init(int n){           //三倍空間初始化
    for(int i=1;i<=n;i++)
    {
        father[i]=i;
        father[i+n]=i+n;
        father[i+n+n]=i+n+n;
    }
}
int find(int x)         //找祖先
{
    if(father[x]==x)
{
        return x;
    }
    else  
    {return father[x]=find(father[x]);}
}
void union_(int x,int y){       //模板
    int i=find(x);
    int j=find(y);
    if(i!=j){father[i]=j;}
}
int main()
{
    int n,m,p;
    cin>>n>>m;
    init(n);
    for(int i=1;i<=m;i++)
    {
        cin>>c>>a>>b;
        if(a>n||b>n)        //判斷符不符題意
        {
            ans++;
            continue;
        }
        if(c==1)            //同類情況
        {
            if(find(a+n)==find(b)||find(a+n+n)==find(b))    //判斷符不符題意
            ans++;
            else
            {
                union_(a,b);                //同類處理
                union_(a+n,b+n);
                union_(a+n+n,b+n+n);
            }
        }
        else
        {
            if(a==b)                //判斷符不符題意
            {
                ans++;
                continue;
            }
            if(find(a)==find(b)||find(a+n+n)==find(b))
            ans++;
            else
            {
                union_(a,b+n+n);            //非同類處理
                union_(b,a+n);
                union_(a+n+n,b+n);
            }
        }
    }
    cout<<ans<<endl;                //輸出
    return 0;
}

總結:

1、並查集要註意關系,不能漏建,不能重建,不然會WA。

2、Find(),merge()的模板要記住,一般進行修改都會在這兩個函數裏改。

3、合並的時候有時要註意方向,不要搞反了。

隨便給點題:

UVA1316 Supermarket

UVA615 Is It A Tree?

UVA1197 The Suspects

P1195 口袋的天空

P1525 關押罪犯

POJ 1988 Parity game
(題解鏈接:大佬博客)

$ ??????????????? ? $ 謝謝觀賞,給個贊唄qwq

並查集:學習總結