並查集應用總結 內置例題

emmm。。。。。。在說其他的之前先給出我的並查集代碼:

struct Union_Find{
    int pa[maxn],stk[maxn],top;
    void Initial(int n) { for(int i=1;i<=n;i++) pa[i]=i; }
    int Find(int x)
    {
        while(x!=pa[x]) stk[++top]=x,x=pa[x];
        int rt=x;
        while(top) pa[stk[top--]]=rt;
        return rt;
    }
    void Merge(int x,int y) { pa[Find(x)]=Find(y); }
    bool Judge(int x,int y) { return Find(x)==Find(y); }
}UF;

————————————————————————————————————————————————————

最優生成樹(kruskal):
【題目描述】這個就不需要了吧。。。。

kruskal的原理我覺得就不用描述了,實際上就是利用並查集來實現一個貪心策略。關於最優生成樹實際上還有很多複雜的性質,這個以後再說吧。
下面是實現的代碼:

void kruskal()
{
    sort(_E+1,_E+M+1,cmp);
    UF.Initial(N);
    int u,v,cnt=0;
    for(int i=1;i<=M;i++)
    {
        u=_E[i].u,v=_E[i].v;
        if(UF.Judge(u,v)) continue;
        UF.Merge(u,v);
        cnt++;
        if(cnt==N-1) break;
    }
}

這是最基礎的實現代碼,還可以在裏面實現很多的操作的。

————————————————————————————————————————————————————

帶權並查集(應用很靈活,僅舉兩個例子):
例題1:朋友與敵人
【問題描述】

      朋友的朋友是朋友
      朋友的敵人是敵人
      敵人的朋友是敵人
      敵人的敵人是朋友

  請你利用上述道理解答下面的問題:

  這個世界的所有國家被兩個強權(CHN和USA)劃分成了兩個敵對陣營,現在各個國家都派出自己的代表到CQYZ參加瓜分利益的會談,已知與會的代表總人數爲N,同一陣營國家的代表是朋友,不同陣營國家的代表是敵人。現在有人給出兩種說法來描述他們之間的關係:

  第一種說法是:1 x y,表示x和y是朋友(1<=x,y<=N)。
  第二種說法是:2 x y,表示x和y是敵人(1<=x,y<=N)。

  此人用上述兩種說法,一句接一句地說出K句話,這K句話有的是真的,有的是假的。當一句滿足下列條件之一時,這句話就是假話:
  1)當前的話與前面的某些真話衝突,就是假話;
  2)當前的話表示x與x是敵人,就是假話。

  你的任務是根據給定的N和K句話,輸出假話總數。

  1<=N<=50000,0<=K<=100000

分析:可以看出是一個邏輯判斷的問題,對於每一對人,可能存在三種關係:互爲朋友、敵人和陌生人。
先分析一下問題,當我們知道了一些人之間的關係的時候,意味着我們可能通過這些人之間的關係來推導出另一些人之間的關係,即題目沒有告訴的關係。而且仔細觀察發現題目描述的法則不就是異或嗎?
這個時候就需要用到並查集的一個重要的功能,我稱之爲“傳遞”。簡單來說,可以互相確認關係的人都放在一個集合中,那麼陌生人就應該處在不同的集合中。每一次給出信息某兩個人x,y是朋友的時候(假設是真話且這兩個人原來是陌生人),我們就合併x,y各自所在的集合。給每個人一個權值表示“並查集中自己和父親的關係”。這樣我們就可以在Find操作中判斷出x,y和各自的代表元的關係,然後再通過x,y之間的關係就可以判斷出兩個代表元之間的關係,從而實現合併。每一次Find的時候都重新更新當前元素和父親(Find的時候就變成了代表元)的關係。


例題2:NOI2002 銀河英雄傳說
題意簡述:給出30000列只有一個元素的隊列每個元素的編號爲其所在列的編號,有兩種操作:1、M x y,表示將第x列的隊列接到第y列的隊列尾部。2、C x y,詢問元素x和y是否在一個隊列中,如果在一個隊列中,輸出它們之間隔了多少個元素,否則輸出-1。

分析:很容易看出這是並查集,關鍵在於如何回答詢問。給每個元素兩個權值:排頭的第幾個以及比自己的兒子排名高多少。爲什麼這樣做呢?很容易發現實際上位置的變化都是相對的,那麼在維護的時候我們只需要用相對的信息來更新原來有的信息就可以了。同時每個代表元還有一個屬性表示以這個元素爲代表元的集合中有多少個元素。題目保證了M操作中x和y不在一個集合中就不用Judge一下了,但是注意很多時候不Judge是要坑死人的。

下面是代碼(不要在意我的優化輸入輸出):

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<set>
#include<map>
#include<queue>
#include<vector>
#include<cctype>
using namespace std;
const int maxn=30005;

int T;
struct Union_Find{
    int pa[maxn],rank[maxn],sz[maxn],add[maxn];
    int stk[maxn],top;
    void Initial(int n)
    {
        for(int i=1;i<=n;i++)
            pa[i]=i,rank[i]=1,sz[i]=1,add[i]=0;
    }
    int Find(int x)
    {
        while(pa[x]!=x) stk[++top]=x,x=pa[x];
        int rt=x,sum=0;
        while(top)
        {
            x=stk[top--];
            pa[x]=rt;
            rank[x]+=sum;
            sum+=add[x];
            add[x]=sum;
        }
        return rt;
    }
    bool Judge(int x,int y) { return Find(x)==Find(y); }
    void Merge(int x,int y)
    {
        int px=Find(x),py=Find(y);
        rank[px]+=sz[py];
        add[px]=sz[py];
        sz[py]+=sz[px];
        pa[px]=py;
    }
    int Val(int x) { Find(x); return rank[x]; }
}UF;

void _scanf(int &x)
{
    x=0;
    char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
void _scanf(char &x)
{
    x=getchar();
    while(x!='C'&&x!='M') x=getchar();
}
int out[10],cnt;
void _printf(int x)
{
    if(x<0) x=-x,putchar('-');
    out[++cnt]=x%10,x/=10;
    while(x) out[++cnt]=x%10,x/=10;
    while(cnt) putchar(out[cnt--]+'0');
}
int main()
{
    freopen("test.in","r",stdin);
    freopen("test.out","w",stdout);
    _scanf(T);
    char op;
    int x,y;
    UF.Initial(30000);
    for(int i=1;i<=T;i++)
    {
        _scanf(op);_scanf(x);_scanf(y);
        if(op=='M') UF.Merge(x,y);
        if(op=='C')
        {
            if(!UF.Judge(x,y)) _printf(-1);
            else _printf(abs(UF.Val(x)-UF.Val(y))-1);
            putchar('\n');
        }
    }
    return 0;
}

————————————————————————————————————————————————————

假刪除:
並查集是並不支持刪除的,所以我們可以假刪除。給被刪除的元素一個新的編號同時在這個元素原來所在的集合中把有關的信息抹去,以後的操作就對這個新元素進行。


暫時差不多就是這麼多了,並查集這個東西真的很靈活,以後發現什麼新的操作會補充的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章