演算法 樹鏈剖分解析

2022-06-20 02:21:15 字數 4363 閱讀 7750

線段樹 \(and\) 樹上基本操作

幾個在樹鏈剖分很重要的概念。

對於乙個父節點,含有節點數最多的兒子稱為重兒子。但重兒子只有乙個,若滿足條件的兒子有多個,則指定其中任意乙個兒子為重兒子。

對於乙個父節點,除了重兒子以為,其餘的都稱為輕兒子。

由父節點與重兒子構成的邊。

由父節點與輕兒子構成的邊。

由重邊構成的鏈。

由輕邊構成的鏈。

重鏈中深度最小的邊為該重鏈的鏈頂。

上述幾個概念具體如下圖:

其中,黃色點為重兒子,藍色點為輕兒子( \(1\) 除外)。黃色邊為重邊,藍色邊為輕邊。通常,乙個單獨的點也看為一條重鏈,那麼重鏈有 \(5\) 條:

對於任意一棵樹有如下性質:從任意一點到根節點的簡單路徑上,共有不超過 \(log2(n)\) 條輕鏈,有不超過 \(log2(n)\) 條輕鏈。

預處理需要使用到兩個 \(dfs\) 。

\(dfs1\) 需要處理:

\(dfs2\) 需要處理:

void dfs1(int now, int father) 	}}

void dfs2(int now, int top)

}

測驗鏈結。

分兩種情況向上爬即可(需要保證 \(dep[tp[x]] <= dep[tp[y]]\)):

\(tp[x]!=tp[y]\) ,即不在一條重鏈上。需要 \(x\) 向上爬出這條重鏈,向上繼續尋找能與 \(y\) 匯合的重鏈點。\(x=fa[tp[x]]\)。

\(tp[x]!=tp[y]\) ,即在一條重鏈上,那麼深度小的就位最近公共祖先。

正確性顯然,以為兩條重鏈不會交於同乙個點。

#include #include using namespace std;

const int maxn = 5e5 + 5;

vectorv[maxn];//vector存圖

int fa[maxn], son[maxn], tp[maxn], sz[maxn], dep[maxn];

int n, m, s;

void dfs1(int now, int father) }}

void dfs2(int now, int top)

}int get_lca(int x, int y)

if(x == y)

return x;

if(dep[x] < dep[y])

return x;

return y;

}int main()

dfs1(s, 0);

dfs2(s, s);

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

return 0;

}

樹鏈剖分通常結合著一些資料結構來進行操作,以為重鏈的 \(dfn\) 為連續的序列。

題目鏈結。

對於一棵樹,有 \(5\) 中操作,根據要求完成操作。

先考慮第二個操作。

設 \(lca\) 為 \(u,v\) 的最近公共祖先,那麼可以將操作二分解為從 \(u\) 到 \(lca\) 的路徑取反,和將從 \(v\) 到 \(lca\) 的路徑取反。

那麼按照上述 \(lca\) 往上爬的過程剛好就可以遍歷完這條路徑一次。

按照點的 \(dfn\) 建造一顆線段樹,來維護點的資訊。

這裡點的資訊是指:這個點與它的父節點的連邊的資訊。

線段樹需要維護的資訊有:最大值,最小值,區間和。

具體的操作二**如下:

void negate(int pos, int l, int r) 

push_down(pos);//傳遞懶標記

if(l <= r(lc(pos)))

negate(lc(pos), l, r);

if(r >= l(rc(pos)))

negate(rc(pos), l, r);

push_up(pos);//修改後更新點的資訊

}void negatepast(int x, int y)

if(x == y)

return;

if(dep[x] < dep[y])

swap(x, y);

negate(1, dfn[son[y]], dfn[x]);//注意是son[y],y與其父節點的連邊並不需要修改

}

查詢操作與其類似,就不一一枚舉了。

#include #include #include #include #include using namespace std;

#define inf 0x3f3f3f3f

const int maxn = 2e5 + 5;

struct segment_tree ;

segment_tree tree[maxn << 2];

vectorv[maxn];//vector存圖

int fa[maxn], son[maxn], dep[maxn], siz[maxn];

int tp[maxn], dfn[maxn];

int s[maxn], t[maxn], w[maxn];

int n, q;

int tim;

void push_up(int pos)

void push_down(int pos)

}void build(int pos, int l, int r)

void negate(int pos, int l, int r)

push_down(pos);

if(l <= r(lc(pos)))

negate(lc(pos), l, r);

if(r >= l(rc(pos)))

negate(rc(pos), l, r);

push_up(pos);

}void negatepast(int x, int y)

if(x == y)

return;

if(dep[x] < dep[y])

swap(x, y);

negate(1, dfn[son[y]], dfn[x]);

}void change(int pos, int x, int c)

push_down(pos);

if(x <= r(lc(pos)))

change(lc(pos), x, c);

else

change(rc(pos), x, c);

push_up(pos);

}int query_sum(int pos, int l, int r)

int sumpast(int x, int y)

if(x == y)

return res;

if(dep[x] < dep[y])

swap(x, y);

res += query_sum(1, dfn[son[y]], dfn[x]);

return res;

}int query_min(int pos, int l, int r)

int minpast(int x, int y)

if(x == y)

return res;

if(dep[x] < dep[y])

swap(x, y);

res = min(res, query_min(1, dfn[son[y]], dfn[x]));

return res;

}int query_max(int pos, int l, int r)

int maxpast(int x, int y)

if(x == y)

return res;

if(dep[x] < dep[y])

swap(x, y);

res = max(res, query_max(1, dfn[son[y]], dfn[x]));

return res;

}void dfs1(int now, int father) }}

void dfs2(int now, int top)

}int main()

dfs1(1, 0);

dfs2(1, 1);

build(1, 1, n);

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

scanf("%d", &q);

string opt;

int a, b;

while(q--)

return 0;

}

演算法入門 樹鏈剖分 輕重鏈剖分

目錄 3.0 求 lca 4.0 利用資料結構維護資訊 5.0 例題 參考資料 資料結構入門 線段樹 發表於 2019 11 28 20 39 dfkuaid 摘要 線段樹的基本 建樹 區間查詢 單點修改 及高階操作 區間修改 單點查詢 區間修改 區間查詢 標記下傳 標記永久化 閱讀全文 樹鏈剖分用...

演算法詳解 樹鏈剖分

1 給你乙個序列,再給你一堆詢問區間,對於每個詢問區間,請你求區間內的最大值 累加和等等。對於這個問題,我們是早就做爛的了,線段樹 樹狀陣列等資料結構都能輕鬆求,這裡不再詳述。2 給你一棵樹,再給你一堆詢問,每次給你兩個點,讓你求兩個點之間的路徑中的點權最大值 點權和等等。對於這個問題,我們很顯然不...

樹鏈剖分演算法整理

樹鏈剖分可以把樹分成若干條鏈,從而維護樹上的路徑資訊。本質思想是把樹剖成可以用線性結構儲存的結構,然後可以資料結構維護。分為三種 重鏈剖分 長鏈剖分 實鏈剖分。以下以重鏈剖分為主。重鏈剖分可以將樹上的任意一條路徑劃分成不超過o l ogn o logn o logn 條連續的鏈,每條鏈上的點深度互不...