並查集是一個維護集合的數據結構
它能夠方便的進行元素集合的合併,並且查詢每個元素屬於哪個集合
並查集更多的在於關係的傳遞性,集合與集合之間常常因爲一個元素的“搭橋”而合併成爲同一個集合
只要1,2,3,A題很簡單
並查集(simple)
在使用並查集時,我們一般通過維護父子關係來完成對集合的控制
若兩個點的最老祖先相同,則在同一個集合內,否則,不在同一個集合內
若要進行合併集合操作,則直接讓一個點的最老祖先只指向另一個點的最老祖先就好了(保證了兩個集合內所有點的最老祖先都是同一個點)
並查集的操作很簡單,一個 ,一個 即可
最簡單的板子:
int find(int x) {
if(fa[x]==x) return fa[x];
else return find(fa[x]);
}
void merge(int x,int y) {
int fx=find(x),fy=find(y);
fa[fx]=fy;
}
顯然, 的最大複雜度爲 ,這是不能接受的
隨便YY一下,你會發現對於一個點來說,反正要找的是他的祖先,而不是找 ,所以我們將 在不斷搜索時同時更新爲他的 ,最終更新爲它的祖先即可
這樣複雜度直線下降,可以做到均攤複雜度複雜度
函數一般來說在很大的範圍是不大於 的
所以可以將其看爲線性的
找 板子:
int find(int x) {
if(fa[x]==x) return fa[x];
else return fa[x]=find(fa[x]);
}
或者簡化版:
int find(int x) {
return fa[x]==x?fa[x]:fa[x]=find(fa[x]);
}
這個東西一般被稱作路徑壓縮
帶權並查集
誒呀,會並查集了?那你會帶權並查集麼?
學完普通的並查集,讓我們來想想如果並查集中的連邊是有邊權的咋辦?
其實很簡單,每個點不僅僅保存一個 ,我們還保存一個 ,意味着當前點到 的距離.在路徑壓縮是或者在查詢時記得將 合併即可
但是注意,原來的簡化寫法就不行了
老老實實的寫遞歸*or非遞歸*版吧
int find(int x) {
if(x!=fa[x]) {
int temp=fa[x];
fa[x]=find(fa[x]);
dis[x]+=dis[temp];
}
return fa[x];
}
可持久化並查集
不錯呀,居然連帶權並查集都搞定了?那就試試可持久化並查集吧!
要學會可持久化並查集,首先得學會可持久化線段樹
接下來實現一下可持久化數組
然後用可持久化數組來維護
恭喜你,你就能夠A掉可持久化並查集
PS:其實本來還有個拆點並查集的,但是因爲很懶貌似很簡單,所以自已意會一下就好啦!
這也就是可以回退操作的並查集哦!