並查集 學習筆記

2022-05-02 03:42:07 字數 3577 閱讀 2242

關於並查集這個神奇的東西,之前也有學習過基本的理論和實現,像最小生成樹什麼的也打過不少,但總感覺自己只會簡單的幼稚的基礎東西,稍微擴充套件一點就炸。這幾天我也好好地學習了一下並查集的一些奇技淫巧。

沒學過並查集的孩子看這裡 __戳我__

之前我會的板子,就是很顯然的維護集合的並與查。板子就是一下子的事:

//這是並

inline void mix(int a,int b)

//初始的時候

for(int i=1;i<=n;++i)fa[i]=i;

//查詢的時候帶上路徑壓縮是最大的優(song)化。按秩合併不值一提,不是特殊情況沒什麼必要寫。

上面就是一些很經典但是很簡單的板子。它已經能解決大部分問題。

下面就是一些並查集的擴充套件了。

1.思路擴充套件。

舉個栗子:noip2010關押罪犯

這個題目困擾了我很久,當初還把它當做2-set問題想過,但實際上這就是一道noip題目。

而這種題目的特點就是:**短,演算法簡單,思維難度較高(除了noip2016,吃×去吧)。

其實說白了還真不複雜,排完序就是乙個並查集的事情。

q:並查集不是只能維護"在乙個集合"的資訊嗎?怎麼維護"不在乙個集合"的資訊呢?

a:是不能維護,但題目是有隱含條件的。"只有兩個監獄",代表只有兩個集合。乙個人在a,那麼他的敵人肯定在b,反之亦然。

q:第一組可以隨便放我理解,但是如果出現了一組從未出現過的矛盾,我們又怎麼處理呢?

a:既然它是第一次出現,那麼它之前的矛盾和它暫時毫無關聯,我們只要把他們當成普通的維護,放在不同的集合就好了。

q:講這麼多,感覺不同並查集還是不可做啊,到底是什麼一種方法資磁呢?

a:這就不得不創新一下思維了。我們可以把"x和y不在乙個集合"巧妙轉化一下,轉化成"x在y的敵人的集合,y在x的敵人的集合"。

這樣在查詢的時候,如果你發現兩個人已經在乙個集合,就肯定不合法,這就是答案了。

在維護的時候呢,就按照上面那句話說的做就好啦!

具體實現下,敵人集合可以通過(x+n)代表,只要將並查集陣列開兩倍就好啦。

如果你開局就給每個人設定了乙個假想敵ri,這個假想敵只和i有矛盾,顯然不會影響答案。

這個時候再處理矛盾就很形象很好理解了。

#include    #include    #include    #include    #include    #include    #include    #define ll long long int

#define ls (x << 1)

#define rs (x << 1 | 1)

using namespace std;

const int n = 200010;

struct data

}rem[n];

int n,m,fa[n],ans;

int gi()

while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();

return x*res;}

inline int find(int x)

int main()

const

1.d=1,x,y

}2.d=2,x,y}}

可以看見具有條件整齊性和對齊性(霧)。

總結:看來noip很喜歡出前十年左右的noi題目弱化版。

#include #include #include #include #include #include #include #define ll long long int

#define ls (x << 1)

#define rs (x << 1 | 1)

using namespace std;

const int n = 50010;

int n,m,fa[n*4],ans;

int gi()

while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();

return x*res;}

inline int find(int x)

int main()

if(kind==1)

else fa[f2]=f1,fa[feat2]=feat1;fa[feated2]=feated1;

}else

int f1=find(x),feat1=find(x+n),feated1=find(x+n+n);

int f2=find(y),feat2=find(y+n),feated2=find(y+n+n);

if(f1==f2 || f1==feat2 || feat1==feated2 || feat1==feat2 || feated1==f2 || feated1==feated2)

else fa[f2]=feat1,fa[feat2]=feated1,fa[feated2]=f1;

}} printf("%d\n",ans);

return 0;

}

2.內容擴充套件

常見的並查集只維護了乙個上級陣列,最多再加乙個秩。但有些喪心病狂的出題人不滿足如此,要你在上面寫出一朵花。

比如說: noi2002 銀河英雄傳說

很明顯是並查集是吧,但是好像還要求乙個深度?

於是就變成了帶邊權的並查集。

帶權並查集:維護當前點到fa的距離d[x]。

事實上,到根的距離dis(x)=d[x]+dis(fa[x])。

路徑壓縮後,dis[fa[x]]變成了d[fa[x]]。

d[x]變成了d'[x]=dis(x)=d[x]+d[fa[x]]。

所以在改fa[x]之前d[x]+=d[fa[x]]就好了。

經過仔細思考後,定義dis為到根的距離,size為一溜船的大小(秩)。

關鍵就在於邊權的維護?

考慮到之前的dis是到自己指向的點的距離,find之後的dis[fa]就是fa到根的距離。

所以就是:dis[x]+=dis[fa];

剩下的就很簡單了。

#include #include #include #include #include #include using namespace std;

const int n = 30010;

int fa[n],dis[n],size[n],m;

inline int abs(int x)

inline int gi()

while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();

return x*res;

}inline int gc()

inline int find(int x)

inline void work1(int u,int v)

inline void work2(int u,int v)

int main()

return 0;

}

還記得有乙個貌似是可撤銷的並查集?哎呀我找不到是哪一題了。

主要思路就是不加路徑壓縮,所以要加按秩合併。

然後把每一次的修改加到乙個棧裡面就好了。

退棧的時候就改回來size和fa就好了。

並查集學習筆記

並查集是一種用來管理資料分組狀況的資料結構,可以進行合併操作,但無法進行分割。並查集的結構 並查集也是用樹形結構來實現的,但不是二叉樹。每個資料,元素對應乙個節點,每個組對應一棵樹。並查集的實現 並查集有幾個基本操作初始化 查詢樹的根 合併x,y所屬集合 判斷x,y是否屬於同一集合。查詢是查詢樹的根...

並查集 學習筆記

並查集是由一組互不相交的集合組成的乙個集合結構,並在此集合上定義了運算union和find。即並查集中的元素本身是集合,他們是某個集合的子集,並查集是由這些集合組成的集合結構。並查集上有兩個最基本的運算,find和union。函式find搜尋給定元素i所在的子集合,並返回該自己喝 union運算將兩...

學習筆記 並查集

這是乙個可以實現合併與查詢 元素間關係判定 用來維護多集合 功能多樣化 的超牛批的可以動態維護的樹形結構。這個演算法只要是實現集合元素關係的型別都可以用到,又是線性時間複雜度,而且最關鍵的是它是個高階資料結構,翻譯過來就是這個可以動態維護,所以我覺得這個用途比單純的演算法要好,實際應用比較大,而且這...