---------------------------------------------------------------------------------------------------by Milkor -----------------------------2015.7.12數組
int delta[maxn];
int f[maxn];
void init()
{
int i;
for(i=0;i<maxn;i++)
{
f[i] = 1;
//init delta
}
}
int find(int a)
{
if(f[a]==a){return a;}
int ta = find(f[a]);
//ta 爲 root. f[a] 爲直接父親節點
//update the delta[a]
return f[a] = ta;
//路徑壓縮。
}函數
void bing(int a,int b)
{
int ta = find(a);
int tb = find(b);
if(ta!=tb) //不是同一個集合中
{
f[ta] = tb;
//update the dalta[ta]
}
}spa
以上是並查集的常見寫法。(沒有維護deep數組,把深度低root 鏈接到 深度高的root上。)it
考慮幾個並查集常見的操做以及問題。原理
操做1:統計當前產生的集合數目。
//假設已經出現了1~N的關係
for(i=1;i<=N;i++)
{
if(f[i] = i)
{
++cnt;
}
}
由於初始化f[i] = i.而root會保留 f[i] = i.統計集合個數其實就是在統計root個數。date
操做2:判斷是否成環。
//在加入a和b的時候,判斷a和b 是不是同根的。若是是同根就是成環了。
bool bing(int a,int b)
{
int ta = find(a);
int tb = find(b);
if(ta!=tb)
{
f[ta] = tb;
return true;
}
else
{
return false;
}
}
//false 是成環了。true是沒有成環。而且合併。程序
操做3:統計集合數目。
//維護一個cou數組。這個維護只要維護在根節點處。不用像種類並查集。要得到各個節點對之間的關係。
init : cou[i] = 1;
bing : cou[tb] += cou[ta];方法
操做4:實現並查集的刪除節點的操做。
//維護一個id數組。對於要刪除的節點。就把該節點的id指向沒有使用的節點處。
/*
原理稍微解釋一下:
假如咱們訪問一個節點是經過一個id數組去訪問的。
一開始:init:id[i] = i.是和咱們一般的是同樣的。
輸入a,b. 那合併操做就是bing(id[a],id[b]).
對於刪除操做 a.那就是令id[a] = cnt++.
//其中cnt一開始賦值爲n.n表示節點數值範圍。這樣cnt指向的就是一個沒有使用的節點。統計
試想一下。若是此時再次輸入a.是否就能訪問到一個新的節點。而且曾經是a的孩子節點也所有都不受影響。而且也再也不是a的孩子了。由於a已是一個新的節點了。
*/集合
操做5:統計一個節點的合併次數。
提出這個是爲了提出並查集維護節點信息的一個核心思想:依託根節點。
這個也是爲了能比較好理解食物鏈之類的種類並查集作的鋪墊。
考慮一下這樣的問題。
一個並查集中有 ta - ab - ac - ad 這樣四個元素。其中ta是根節點。
另一個並查集中有 tb - bb - bc -bd 這樣四個元素。其中tb是根節點。
如今要讓f[ta] = tb. 讓ta做爲tb的孩子節點鏈接上去。
那麼一定ta 集合中的元素都要增長 1 次移動次數。
那麼如何作到集合中每一個元素都增長 1 次移動次數呢?咱們天然而然會想到整個集合的關係樞紐ta.
若是咱們讓move[ta] += 1.
而且當咱們find的時候。再把根節點的信息更新到咱們須要的孩子上。就能得到孩子節點的完整信息了。這有點像線段樹裏的lazy標誌。
int find(int a)
{
if(f[a]==a){return a;}
ta = find(f[a]);
move[a] += move[f[a]];
// 是把信息一層一層傳下去的。因此必定是 += move[f[a]].得到完整信息以後。再路徑壓縮是符合結果的。
// 若是你考慮一個問題就是若是我一直作find(a)操做。那麼move[a]是否會發生變化呢?答案是不會的。這個問題就留給你本身考慮吧。
return f[a] = ta;
}
void bing(int a,int b)
{
int ta = find(a);
int tb = find(b);
if(ta!=tb){f[ta] = tb;move[fa]=1;}
//由於做爲根節點只可能移動一次。因此只用更新=1.
}
操做6:關於delta[]數組的更新和維護。即所謂帶權值的並查集
delta[i] 表示 i和ti的關係。// ti = find(i).
這裏體現了並查集維護節點信息的一個核心思想:依託根節點。
由於題目要求的是咱們能得到任意對節點的關係。而咱們只要得到節點對各自的根節點的關係。
就能通過一系列轉換來得到二者的關係。
重點:
維護delta[i]:
提一下種類並查集中的類向量運算:
一個問題:A和B是不一樣的。B和C是不一樣的。那麼A和C是同類的。
這個問題是:Find them, Catch them 中的關係聯繫。
咱們令X和Y是不一樣的爲1。令X和Y是相同的爲0。
咱們看看對於這個問題這樣的設法是否知足所謂的類向量運算
A->B 爲不一樣爲1. (在本問題中A->B和B->A是同樣的(由於A和B不相同,與B和A是不相同是一個意思。一樣A和B相同也是如此)。在這點上實際上是不符合向量運算。對於這個問題咱們下面再談)
B->C 爲不一樣爲1.
那麼A->C?.
A->C = A->B + B->C = 1 + 1 = 2 對於這個咱們對2取模。這是爲了保證讓咱們的關係是有意義的同時。
其實也是爲了讓運算知足上述關係。
A->C = 0 是同類。因此知足咱們的運算定義。
以上要得到A->C 的關係。能夠相似向量中的繞一圈加法即A->C = A->A1 + A1->A2 + A2->A3 + A3... + An->C 稱之爲知足類向量加法。
若是有了這個。那麼咱們就能夠很方便的作一個節點的delta的更新。
int find(int a)
{
if(f[a]==a){return a;}
int ta = find(a);
delta[a] = (delta[a] + delta[f[a]]) % 2;
return f[a] = ta;
}
對於這句delta[a] = (delta[a] + delta[f[a]]) % 2;
而這裏的f[a] 實際上是原來的根節點ta'.
否則的話 delta[a] != (delta[a] + delta[f[a]]) % 2 。
畫個圖模擬一下並查集從無到有的建立過程就能夠知道這點。
一開始
1->2
delta[1] 存儲的是1->2 的關係信息。
1->2->3
2鏈接到3.
更新delta[2]的信息存儲的是2->3的關係信息。而此時delta[1]始終仍是1-2的。
因此delta[1] 更新爲( delta[1] + delta[2] ) % 2.
固然你也能夠多鏈接幾回以後。而後再find 1 會發現1的信息更新的仍是正確的。
原理同就是。
a->ta = a->ta' + ta'->ta
bing函數。更新根節點(信息維護:依賴根節點)
void bing(int a,int b)
{
int ta = find(a);
int tb = find(b);
if(ta != tb)
{
f[ta] = tb;
delta[ta] = ( delta[a] + delta[b] + (a-b的關係) ) % 2;
}
}
delta[ta] = ( delta[a] + delta[b] + (a-b的關係) ) % 2;
理解爲: ta->a + a->b + b->tb.
在本題中ta->a 實際意義上其實就是a->ta.
給出兩個節點的關係判斷:
此時若是兩個節點在一個並查集裏面並非表明着這兩個節點是同類關係。而是說這兩個節點可以判斷出關係。
要得到a->b的關係。
首先判斷是不是同根。若是不是,說這兩個節點還不能得出關係。
若是是:
咱們能夠經過a->b = a->t + t->b = a->t + b->t = (delta[a] + delta[b]) % 2 若是是0 那麼就是相同。若是是1那麼就是不一樣了。
考慮完這個問題。再考慮一個經典的食物鏈的問題。
問題核心是這樣的:A吃B B吃C 那麼C就吃A
咱們該如何設值可以比較好地讓咱們進行關係的更新呢?若是設出來的值能過知足類向量加法運算是最最好不過了。
其實這是有的。
讓 X吃Y 即 X->Y 爲 1.
X和Y同類 就是 0.
Y吃X 即 X->Y 爲 2.
這個時候你能夠嘗試驗證同樣是否知足類向量加法運算。答案固然是知足的。
而且比上題目有意思的是。這個還知足向量的自反性。
X->Y = 1.
Y->X = -1.爲了讓其在0~2範圍內。
Y->X = (-1+3)%3 = 2.知足咱們上述的關係。(這裏的操做實際上是數論上的同餘定理的小使用)
程序怎麼寫就不用說了吧。由於這樣設值知足了類向量加法了已經。就是若是取反。要減去那個值。
其實若是能理解到這。上面留下來的問題也就已經解決掉了。咱們並不須要整套的知足向量運算。甚至咱們其實不用知足所謂的向量加法。
咱們只是爲了咱們的關係可以比較好地發生轉移。
好比食物鏈那個題目。若是改成A->B,B->C 那麼能夠得到A->C。你要怎麼作處理?
其實很簡單。只要設X->Y爲1.
A->C = A->B|B->C
雖然仍是知足形式上的類向量加法。
可是你必須知道的是咱們只是爲了咱們的關係更好地轉移而這樣作的。並且能夠適應多元的運算。
不然的話你用邏輯窮舉全部可能性。也是能夠的。只不過這裏把邏輯運算作成了相似的加法運算。
好比隨意地、
我要給A->C賦關係的時候。我能夠特判。A->B 的關係如何如何。B->C的關係如何如何。那麼A->C的關係就是如何如何。
固然這樣會很麻煩。可是也不是不能夠。因此本質上就是爲了更好的關係轉移而已。而後加上維護並查集信息的核心思想。
其實我很想可以有一個方法比較好地推導出咱們該設的值。而不是無心義地嘗試(儘管你會發現咱們要設的值老是0開始而後幾個天然數)。