Kruskal重構樹學習筆記

2022-08-16 03:12:14 字數 4384 閱讀 9333

這裡是kruskal重構樹學習筆記。

kruskal重構樹,是用於求出有關一張圖中,某點僅經過邊權 \(\leq\) 某個值 \(v\) 的邊所得到的子圖的有關資訊的工具。

但事實上,其應用還有更多。

我們先講述其構造方法:

將所有邊按照邊權遞增排序。

依次列舉每一條邊。假如此時邊的兩個端點處於兩個不同集合中,按照常規kruskal演算法,此時應該將該邊加入mst,並在dsu上合併這兩個點;然而,在kruskal重構樹演算法中,我們將先新建乙個點,然後將 \(x,y\) 所在子樹的根當作該新建點的兩個兒子。同時,將該新建點的權值設作該邊的邊權。

具體而言,假設我們有三個點,以及三條邊 \((1,2,1),(2,3,2),(1,3,4)\)。下面我們構造重構樹:

第一條邊 \((1,2,1)\),兩個端點 \((1,2)\) 不位於同一子樹中,故新建點 \(4\),點權為 \(1\),兩個兒子為 \(1\) 和 \(2\)。

第二條邊 \((2,3,2)\),兩個端點 \((2,3)\) 不位於同一子樹中,故新建點 \(5\)。此時,\(2\) 所在子樹的根是 \(4\),\(3\) 所在子樹的根是 \(3\),故連邊 \(5\rightarrow4,5\rightarrow3\),並將 \(5\) 的點權設作 \(2\)。

第三條邊 \((1,3,4)\),兩個端點 \((1,3)\) 已都位於 \(5\) 的子樹中,故不作任何操作。

就此我們構造出了kruskal重構樹。

它有什麼性質呢?

其成乙個大根堆

因為我們加入的邊權遞增,故乙個點的權值一定大於其兩個兒子的權值,假如我們把原圖中點的權值設作 \(\infty\) 的話。

原圖中兩點間所有路徑中,路徑上所有邊的最大值最小的那一條路徑的上述最大值為重構樹上兩點lca的權值。

首先,路徑上所有邊的最大值最小的路徑,一定是mst上的路徑;而重構樹上兩點間路徑唯一等價於mst上路徑;又重構樹有大根堆性質,故最大值一定出現在lca處。

這也就給我們提供了截出到 \(x\) 只經過權值不超過 \(v\) 的點集的方法:在重構樹上樹上倍增找到最後乙個權值不大於 \(v\) 的 \(x\) 的祖先 \(y\),然後 \(y\) 子樹中所有節點即為該點集。因為 \(x\) 到 \(y\) 子樹內任何點的lca一定也在 \(y\) 的子樹內,\(x\) 到 \(y\) 子樹外任何點的lca一定也在 \(y\) 的子樹外,按照大根堆性質,前者一定在點集內,後者一定在點集外。

kruskal重構樹的複雜度為 \(o(n\log n)\),來自於排序和不能按秩合併,只能路徑壓縮的冰茶姬。

可以被直接當作模板了。

樹上倍增到需要的節點,這樣所有合法的點集就變成了重構樹上某節點的子樹中所有節點,也即dfs序上一段區間。於是問題變為經典的區間 \(k\) 大數問題,直接主席樹帶走。

時間複雜度 \(o(n\log n)\)。

**:

#includeusing namespace std;

int n,m,q,a[100100],dsu[200100],num,val[200100],anc[200100][20],l[200100],r[200100],rev[100100],cnt,rt[100100],tot;

struct node

void dfs(int x,int fa)

int doubling(int x,int k)

struct segtreeseg[3201000];

#define mid ((l+r)>>1)

void modify(int &x,int y,int l,int r,int p)

int main()

for(int i=1;i<=num;i++)if(dsu[i]==i)dfs(i,0);

sort(u.begin(),u.end()),u.resize(m=unique(u.begin(),u.end())-u.begin());

for(int i=1;i<=n;i++)a[i]=lower_bound(u.begin(),u.end(),a[i])-u.begin()+1;

for(int j=1;j<=19;j++)for(int i=1;i<=num;i++)anc[i][j]=anc[anc[i][j-1]][j-1];

for(int i=1;i<=n;i++)modify(rt[i],rt[i-1],1,m,a[rev[i]]);

for(int i=1,x,y,z;i<=q;i++)

node(){}

node(int x,int y,int z)

}e[400100];

priority_queue>q;

void dijkstra()

}int find(int x)

void dfs(int x,int fa)

int doubling(int x,int k)

int main()

for(int i=1;i<=n;i++)v[i].clear();

dfs(num,0);

for(int j=1;j<=19;j++)for(int i=1;i<=num;i++)anc[i][j]=anc[anc[i][j-1]][j-1];

scanf("%d%d%d",&q,&k,&s),lasans=0;

for(int i=1,x,y;i<=q;i++)

} return 0;

}

kruskal重構樹還可以用來優化dp哦~~

結論1.最優情形下,當我們向乙個節點」捐贈「後,我們不會再次訪問該節點。

明顯,如果再次訪問,則在再次訪問時再捐獻一定不更劣。

我們定義 \(c_x=\max(0,a_x-b_x)\),則約束等價於離開 \(x\) 點時剩餘錢數不小於 \(c_x\)。

明顯我們肯定是希望從大到小地依次給每個 \(c_x\) 捐贈,因為反正離開 \(x\) 時無論如何都會剩下 \(c_x\),如果先給大的捐贈,這剩下的錢說不定就在之後捐出去了;但是如果後給大的 \(c_x\) 捐贈,這剩下的錢就有可能爛在手裡捐不出去了。

但是,這只是希望,並不代表我們真的做得到,因為在從乙個 \(c_x\) 到另乙個 \(c_y\) 的過程中,我們說不定會路過乙個擁有更大 \(c_z\) 的點 \(z\) 而導致需要更多的入場費。

但是,我們發現,從 \(c_x\) 到 \(c_y\) 的本質是讓路徑最大值最小,同kruskal重構樹的本質一致。

為了讓重構樹也可以應用於點權,我們將連線兩點 \((u,v)\) 的邊權設作 \(\max(c_u,c_v)\)。這樣,我們便可建出重構樹來。

設對於重構樹上乙個非原圖節點,其 \(c_x\) 為建出重構樹時的邊權。

設 \(f_x\) 表示若我們給重構樹上節點 \(x\) 的子樹內所有節點全都被捐贈過,所需要的最小錢數。

明顯,根據結論1,我們捐贈的路徑一定是 \(\text\rightarrow x\rightarrow\text\),不可能出現兩個兒子子樹內的節點全捐完後再來捐 \(x\) 的情形,因為從乙個兒子中節點走到另乙個兒子中節點的最優路徑必經過 \(x\),故該路徑需要的最大剩餘錢數也是 \(c_x\),而在乙個兒子內部亂跑的最大錢數一定不大於 \(c_x\)。

因為我們離開 \(x\) 時剩餘錢數一定不小於 \(c_x\),而憑藉著這麼多錢在第乙個兒子裡一定可以隨便亂跑而不必多帶錢,因此設 \(s_x\) 表示 \(x\) 節點子樹中所有節點的 \(b_x\) 之和,則從第乙個兒子 \(l\) 出來時所需費用一定僅需 \(s_l\) 即可。然後,處理 \(x\) 本身以及另乙個兒子 \(r\) 所需費用,為 \(\max(f_r,c_x)\),因為要保證經過 \(x\) 的過路限制以及付給 \(r\) 子樹中點的費用。於是總代價為 \(s_l+\max(f_r,c_x)\)。同理,\(r\rightarrow x\rightarrow l\) 也是一條合法路徑,也可以貢獻為備選答案。

對於葉子節點,其 \(f\) 直接設作 \(b_x+c_x\) 即可。

時間複雜度 \(o(n\log n)\)。

**:

#includeusing namespace std;

typedef long long ll;

int n,m,b[100100],c[100100],dsu[200100],num,val[200100],ch[400100][2];

ll f[200100],s[200100];

struct node

edge(int u,int v,int w)

};struct mst

void dfs(int x)

int doubling(int x,int lim)

void kruskal()

for(int i=1;i<=n;i++)

for(int i=1;i<=q;i++)printf("%d\n",!!res[i]);

return 0;

}

Kruskal重構樹 學習筆記

kruskal重構樹 性質 1.是乙個小 大根堆 由建樹時邊權的排序方式決定 2.lca u,v 的權值是原圖u到v路徑上最大 小邊權的最小 大值 由建樹時邊權的排序方式決定 kruskal重構樹 建樹 模仿kruskal的過程,先將邊權排序 排序方式決定何種性質接下來說明 依次遍歷每條邊 若該邊連...

學習筆記 Kruskal重構樹

kruskal 求最值生成樹時需要通過邊合併兩個之前不相連的連通塊 這時候通過建立虛點表示兩點之間有連邊,同時將邊的資訊記錄到虛點上 所以如果查詢原圖上的兩點間路徑上的極值,可以考慮維護重構樹路徑上點權的資訊 大概長成這樣子,變數名還是比較大眾化的 nodes n for int i 1 i m i...

kruskal重構樹學習筆記

8102ioncc 中考到了,本蒟蒻不會,所以學一下。kruskal 求最小 大 生成樹,樹上求 lca kruskal 重構樹可以解決瓶頸路問題 如 noip2013 d1t3 貨車運輸,可以當做模板題來做,本文中也將此題作為例題 我們來思考一下 kruskal 求最小 大 生成樹的過程 後文中以...