關於並查集這個神奇的東西,之前也有學習過基本的理論和實現,像最小生成樹什麼的也打過不少,但總感覺自己只會簡單的幼稚的基礎東西,稍微擴充套件一點就炸。這幾天我也好好地學習了一下並查集的一些奇技淫巧。
沒學過並查集的孩子看這裡 __戳我__
之前我會的板子,就是很顯然的維護集合的並與查。板子就是一下子的事:
//這是並
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運算將兩...
學習筆記 並查集
這是乙個可以實現合併與查詢 元素間關係判定 用來維護多集合 功能多樣化 的超牛批的可以動態維護的樹形結構。這個演算法只要是實現集合元素關係的型別都可以用到,又是線性時間複雜度,而且最關鍵的是它是個高階資料結構,翻譯過來就是這個可以動態維護,所以我覺得這個用途比單純的演算法要好,實際應用比較大,而且這...