1. 程式人生 > >Kruskal重構樹學習筆記+BZOJ3732 Network

Kruskal重構樹學習筆記+BZOJ3732 Network

所有 最小值 按秩合並 筆記 inf 復雜 dep space printf

今天學了Kruskal重構樹,似乎很有意思的樣子~


先看題面:

BZOJ

題目大意:$n$ 個點 $m$ 條無向邊的圖,$k$ 個詢問,每次詢問從 $u$ 到 $v$ 的所有路徑中,最長的邊的最小值。

$1\leq n\leq 15000,1\leq m\leq 30000,1\leq k\leq 20000$。


我相信你們看見這題的想法和我一樣:

貨車運輸!最小生成樹上LCA一下就行了!時間復雜度 $O(mlogm+n\alpha(n)+klogn)$。(我的LCA是樹鏈剖分)

但是這題有另一種做法:Kruskal重構樹

(這種用到路徑最長邊值等一般都能用Kruskal重構樹)


Kruskal重構樹是什麽?和Kruskal求最小生成樹有什麽關系?

我們先來看一下Kruskal重構樹的一些性質。

Kruskal重構樹是由原圖的 $n$ 個點,新添加的 $n-1$ 個點和 $2n-2$ 條邊構成的樹。

(以下討論的Kruskal重構樹都是最小Kruskal重構樹,最大Kruskal重構樹反之亦然)

下面是一個圖和它的Kruskal重構樹:(圓點是原圖中的點,方點是新加的點)

技術分享圖片

它的性質有:

  1. 一棵二叉樹
  2. 葉子結點都是原圖中的點,沒有點權;非葉子結點都是新加的點,有點權
  3. 舊點 $u,v$ 兩點間(不包括 $u,v$)的所有節點(都是新點)的點權最大值為原圖中 $u,v$ 兩點間所有路徑的最長邊的最小值
  4. 新點構成一個堆(不一定是二叉堆),在最小Kruskal重構樹中是大根堆
    (最大Kruskal重構樹中是小根堆)
  5. 結合性質3和4,舊點 $u,v$ 兩點的LCA(是新點)權值為原圖中 $u,v$ 兩點間所有路徑的最長邊的最小值

比如點 $1,3$ 的LCA權值為 $3$,恰好是原圖中 $1,3$ 兩點間所有路徑的最長邊的最小值(邊 $(2,3)$)。


Kruskal重構樹怎麽求呢?

  1. 找到一條邊權最小的,且沒有被遍歷過的邊。
  2. 如果這條邊連接的兩個點目前沒有在一個聯通塊,那麽新建一個節點,點權為該邊的邊權,左右兒子設為這兩個點的最遠祖先。(通過設置兒子為最遠祖先合並聯通塊且不破壞性質)
  3. 重復1,2直到遍歷完所有的邊。

畫個圖了解一下,下圖中紅點是該邊連接的兩個點,綠點和綠邊是添加的點和邊:(點我食用這張圖會更佳)

技術分享圖片

如何用代碼實現?像普通的Kruskal一樣,維護一個路徑壓縮並查集,其中 $fa$ 數組不止止是判斷在哪個集合,更是標記在重構樹上的父親。這也是為什麽要路徑壓縮而不是按秩合並的原因,因為這樣可以更快求得最遠祖先。

每處理一條邊,先判連通性,如果不連通那麽把這兩個點的最遠祖先的 $fa$ 都設成新加的點,同時連邊即可。註意,不能反過來,否則重構樹在並查集上的結構會被破壞。

至於舊點之間,怎麽連邊,連那些邊?參見性質2。

(其實就是不用連邊,我好啰嗦啊


回到原題:建出最小Kruskal重構樹,每次LCA一下即可。

這裏順便說一下,如果原圖不保證聯通怎麽辦,如何預處理?(以下的代碼中我也判了兩點不連通的情況)

兩點不連通可以用建Kruskal重構樹中的並查集知道。而預處理,每次如果一個點沒有dfs到,那麽從這個點的最遠祖先dfs一遍(也可以用並查集知道)。

為什麽要最遠祖先呢?因為這樣是對的且省時間,均攤 $O(\alpha(n))$。自己感受一下

(洛谷上貨車運輸那題是有不連通的圖的測試點,我寫重構樹時第一次沒有從最遠祖先開始dfs,感受一下:)技術分享圖片

時間復雜度也是 $O(mlogm+n\alpha(n)+klogn)$。但是學了一個新算法不是很好嗎?

(讀者:浪費時間,散了散了,吃雞去了)


代碼:(附帶判斷不連通)

技術分享圖片
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 struct edge1{    //一開始的邊,方便排序
 4     int u,v,w;
 5     bool operator<(const edge1 e)const{
 6         return w<e.w;
 7     }
 8 }e1[30030];
 9 struct edge2{    //重構樹上的邊(雖然只有兩個兒子但這樣寫舒服)
10     int to,nxt;
11 }e2[30030];    //重構樹的點數是原圖的約2倍,但是沒有雙向邊
12 int n,m,q,el1,el2,cnt,root;
13 int u_fa[30030],w[30030],head[30030];    //u_fa是並查集的fa,w是新節點的權值
14 int dep[30030],fa[30030],son[30030],size[30030],top[30030];
15 inline void add1(int u,int v,int w){    //原圖加邊
16     e1[++el1]=(edge1){u,v,w};
17 }
18 inline void add2(int u,int v){    //重構樹加邊
19     e2[++el2]=(edge2){v,head[u]};
20     head[u]=el2;
21 }
22 int getfa(int x){    //路徑壓縮
23     return x==u_fa[x]?x:u_fa[x]=getfa(u_fa[x]);
24 }
25 void dfs1(int u,int f){
26     fa[u]=f;
27     dep[u]=dep[f]+1;
28     size[u]=1;
29     int maxson=-1;
30     for(int i=head[u];i;i=e2[i].nxt){
31         int v=e2[i].to;
32         if(v==fa[u]) continue;
33         dfs1(v,u);
34         size[u]+=size[v];
35         if(size[v]>maxson) maxson=size[v],son[u]=v;
36     }
37 }
38 void dfs2(int u,int topf){
39     top[u]=topf;
40     if(!son[u]) return;
41     dfs2(son[u],topf);
42     for(int i=head[u];i;i=e2[i].nxt){
43         int v=e2[i].to;
44         if(v==fa[u] || v==son[u]) continue;
45         dfs2(v,v);
46     }
47 }
48 int calc(int u,int v){
49     if(getfa(u)!=getfa(v)) return -1;    //如果不連通
50     while(top[u]!=top[v]){
51         if(dep[top[u]]<dep[top[v]]) swap(u,v);
52         u=fa[top[u]];
53     }
54     return dep[u]<dep[v]?w[u]:w[v];    //LCA的權值
55 }
56 int main(){
57     scanf("%d%d",&n,&m);
58     for(int i=1;i<=m;i++){
59         int u,v,w;
60         scanf("%d%d%d",&u,&v,&w);
61         add1(u,v,w);
62     }
63     sort(e1+1,e1+m+1);    //從小到大排序
64     for(int i=1;i<=2*n-1;i++) u_fa[i]=i;
65     cnt=n;
66     for(int i=1;i<=m;i++){
67         int u=e1[i].u,v=e1[i].v;    //操作的兩個點
68         u=getfa(u);v=getfa(v);
69         if(u==v) continue;    //已經聯通,跳過
70         w[++cnt]=e1[i].w;    //新建一個點
71         u_fa[u]=u_fa[v]=cnt;    //設置父親!
72         add2(cnt,u);add2(cnt,v);    //加邊(不需要雙向邊)
73     }
74     for(int i=1;i<=2*n-1;i++)
75         if((w[i] || i<=n) && !dep[i])    //如果這個點存在並且沒有被搜過
76 //解釋一下這個條件,w[i]是表示這個點有沒有權值,如果有就是新點(為什麽要判斷這個呢,因為圖不連通,可能點個數並沒有達到2n-1);i<=n表示這個是不是舊點;!dep[i]表示沒被搜過
77             dfs1(u_fa[i],0),dfs2(u_fa[i],0);    //那就搜唄
78     scanf("%d",&q);
79     for(int i=1;i<=q;i++){
80         int u,v;
81         scanf("%d%d",&u,&v);
82         printf("%d\n",calc(u,v));
83     }
84 }
Kruskal重構樹

另外今年的NOI2018D1T1正解也是Kruskal生成樹,以後再慢慢杠吧,留個坑等著補題解。

Kruskal重構樹學習筆記+BZOJ3732 Network