其實本人學樹剖時還徹底沒到學樹剖的水平,但本着在圖論這一塊的興趣就看了看,結果發現樹剖還挺簡單?node
樹剖是什麼呢?看看它的全稱:「樹鏈剖分」。顧名思義,就是把一棵樹解剖成一個個鏈子咯。而後在每條鏈上維護須要的信息,好比最小值、最大值、權值和什麼的。這棵樹的紅線就是剖出來的鏈條:特別地,一個點也算一條鏈.ios
可是問題來了,假如樹上有兩個點,我怎麼知道他們倆是否是在同一條鏈子上呢?因此咱們還須要用一個東西來給鏈子上的全部節點染色,這樣就能夠回答剛纔的問題了。怎麼染呢?咱們想一個問題,若是我要查樹上兩個點之間節點的權值的最大值,怎麼查?這裏就有兩種狀況:算法
1.這兩個點在同一條鏈上,這樣的話就能夠直接查出鏈上兩點的區間中的最小值了。數組
2.這兩個點不在同一條鏈上。咱們就可讓兩個點不斷往上跳,直到跳到他們屬於同一條鏈爲止。而樹剖的關鍵之一就是這個跳法。若是樸(bao)素(li)一點,怎麼跳?那固然是一個點一個點地往上跳,那麼時間複雜度就是O(N),對於m次查詢就是O(NM)。那我還要樹剖有個屁用。。。因此要換一種優雅點的。如今咱們不是把樹都剖成鏈了嗎?爲何不一條鏈一條鏈地跳呢?咱們能夠首先記錄下當前節點u所在的鏈的頂端top[u],若要跳出這條鏈,就讓u=fa[top[u]](不是top[u])。就像這樣不斷地跳,直到兩個點的top相同爲止。app
以上就是樹剖的基本思路。那麼咱們爲何要樹剖這種東西呢?「有時能夠用來暴力。」——學校一個大佬。樹剖確實是暴力,只不過很是地優雅。若是一道題須要查詢樹上兩點的區間中的某些信息,咱們能夠直接查這個區間,而不是枚舉區間的每一個點。因此咱們須要記錄下樹上的每個區間,用線段樹來維護,而這些區間就是上面所說的鏈條了。這樣一來,每次查詢的複雜度就降爲O(logN)了。固然這是最理想的狀況。而爲了讓咱們的樹剖的複雜度儘可能接近這個等級,咱們就須要想辦法把樹剖成logN條鏈子。因此這裏引入一個概念:重鏈剖分。函數
所謂重鏈,給人的感受就是這條鏈特別重。也就是這條鏈的節點數特別多了。而重鏈剖分的過程就是:對於當前節點u,找出以它的子節點爲根的子樹中節點最多的一棵子樹,假如這顆子樹的根爲v,那麼就給這條鏈加上節點v,並遞歸下去,對v也如法炮製。這樣構造出的鏈就很接近logN個。而u的這個子節點v有個定義,叫作u的「重兒子」。ui
那麼,怎麼讓一條鏈所表示的區間內的全部節點都是連續的整數呢?想一想剛纔的操做,咱們在建鏈時是沿着鏈遞歸下去的,若是咱們這時記下每一個節點的時間戳的話,是否是這條鏈上的時間戳就是一串連續的整數了呢?這樣就解決了剛纔的問題。因此,咱們在剖樹時,須要兩次DFS,第一次找出每一個節點的重兒子,第二次沿着重兒子走下去,記下每一個點被遍歷到的時間戳:spa
void dfs_getson(int u){//第一次DFS size[u] = 1; for(int i = head[u]; ~i; i = e[i].next){ int v = e[i].to; if(v == fa[u]) continue;//注意不能往回走 fa[v] = u; dep[v] = dep[u] + 1;//計算深度,等會要講. dfs_getson(v); size[u] += size[v]; if(size[v] > size[son[u]]) son[u] = v;//記錄u的重兒子. } }
void dfs_rewrite(int u, int tp){//第二次DFS top[u] = tp, dfn[u] = ++tot, id[tot] = u;//id爲dfn的反函數,因爲鏈上的區間由dfn組成,爲了知道鏈上某個點的編號咱們記錄下id.top爲u所在鏈的頂端 if(son[u]) dfs_rewrite(son[u], tp);//優先往重兒子走. for(int i = head[u]; ~i; i = e[i].next){ int v = e[i].to; if(v != fa[u] && v != son[u]) dfs_rewrite(v, v);//往不是重兒子的兒子(輕兒子)走 } }
像這樣,一棵樹就被咱們剖成了鏈。不過這些鏈如今尚未什麼用,咱們須要用線段樹來維護它們。因此首先是構造。這裏用區間最大值爲例,其它的相似:設計
void build(int d, int l, int r){ t[d].l = l, t[d].r = r; if(l == r){ t[d].mmax = val[id[l]]; return; }//要注意這裏的lr都是dfn值 int mid = l + r >> 1; build(d << 1, l, mid);//構造左兒子 build(d << 1 | 1, mid + 1, r);//構造右兒子 t[d].mmax = max(t[d << 1].mmax, t[d << 1 | 1].mmax); } //main函數中 build(1, 1, tot);
而後是單點查詢:code
int getmax_vertex(int d, int x){ if(t[d].l == t[d].r) return t[d].mmax; int mid = t[d].l + t[d].r >> 1; if(x <= mid) return getmax_vertex(d << 1, x); else return getmax_vertex(d << 1 | 1, x); } //main函數中 ans = getmax_vertex(1, dfn[u]);
單點修改:
void change_vertex(int d, int x, int w){ if(t[d].l == t[d].r){ t[d].mmax = w; return; } int mid = t[d].l + t[d].r >> 1; if(x <= mid) change_vertex(d << 1, x, w); else change_vertex(d << 1 | 1, x, w); t[d].mmax = max(t[d << 1].mmax, t[d << 1 | 1].mmax); } //main函數中 change_vertex(1, dfn[u], w);
須要注意的是對區間的操做。其實上面已經提到了,若區間的兩個端點u,v在同一條鏈上,那麼直接操做就能夠了。而不在一條鏈上時,只須要跳到同一條鏈上便可。那麼誰跳呢?假如咱們讓top深度小的點跳,深度只能越跳越小,永遠小於另外一個點,直到跳到根節點也什麼也作不了(能夠畫個圖來理解)。因此咱們應該選擇top深度大的開始跳,邊跳邊對跳過的鏈操做,最後跳到同一條鏈時再操做一次。
因而就有了區間查詢(延遲標記就懶得寫了):
int getmax_xtoy(int d, int l, int r){ if(l <= t[d].l && t[d].r <= r) return t[d].mmax; if(t[d].f) down(d); int mid = t[d].l + t[d].r >> 1, ans = 0; if(l <= mid) ans = max(ans, getmax_xtoy(d << 1, l, r)); if(r > mid) ans = max(ans, getmax_xtoy(d << 1 | 1, l, r)); return ans; } //main函數中 int ans = 0; while(top[u] != top[v]){ if(dep[top[u]] > dep[top[v]]) swap(u, v);//從深度大的開始跳 ans = max(ans, getmax_xtoy(1, dfn[top[v]], dfn[v])); v = fa[top[v]];//注意要調到頂端的父親去 } if(dep[u] > dep[v]) swap(u, v); ans = max(ans, getmax_xtoy(1, dfn[u], dfn[v]));
以及區間修改:
void change_xtoy(int d, int l, int r, int w){ if(l <= t[d].l && t[d].r <= r){ t[d].mmax = w; t[d].f = w; return; } if(t[d].f) down(d); int mid = t[d].l + t[d].r >> 1; if(l <= mid) change_xtoy(d << 1, l, r, w); if(r > mid) change_xtoy(d << 1 | 1, l, r, w); t[d].mmax = max(t[d << 1].mmax, t[d << 1 | 1].mmax); } //main函數中 while(top[u] != top[v]){ if(dep[top[u]] > dep[top[v]]) swap(u, v); change_xtoy(1, dfn[top[v]], dfn[v], w); v = fa[top[v]]; } if(dep[u] > dep[v]) swap(u, v); change_xtoy(1, dfn[u], dfn[v]);
沒錯,樹鏈剖分就是個碼量驚人的東西。
如今咱們能夠愉快地刷例題了。
如題,已知一棵包含N個結點的樹(連通且無環),每一個節點上包含一個數值,須要支持如下操做:
操做1: 格式: 1 x y z 表示將樹從x到y結點最短路徑上全部節點的值都加上z
操做2: 格式: 2 x y 表示求樹從x到y結點最短路徑上全部節點的值之和
操做3: 格式: 3 x z 表示將以x爲根節點的子樹內全部節點值都加上z
操做4: 格式: 4 x 表示求以x爲根節點的子樹內全部節點值之和
輸入格式:
第一行包含4個正整數N、M、R、P,分別表示樹的結點個數、操做個數、根節點序號和取模數(即全部的輸出結果均對此取模)。
接下來一行包含N個非負整數,分別依次表示各個節點上初始的數值。
接下來N-1行每行包含兩個整數x、y,表示點x和點y之間連有一條邊(保證無環且連通)
接下來M行每行包含若干個正整數,每行表示一個操做,格式以下:
操做1: 1 x y z
操做2: 2 x y
操做3: 3 x z
操做4: 4 x
輸出格式:
輸出包含若干行,分別依次表示每一個操做2或操做4所得的結果(對P取模)
這不是模板題。這題涉及了區間修改、區間查詢,以及——子樹修改和查詢???這就是這題的難點了。子樹並不僅是簡單的一條鏈,這怎麼辦呢?實際上子樹的東西還簡單地可憐。想一想,咱們在對區間操做時的對象等同於一些連續的dfn值,而子樹呢?子樹的全部點的dfn值不也是連續的一串嗎?因此咱們在第二次搜索時不只記錄下節點x的dfn值,也記下以x爲根的子樹的dfn最大值:
void dfs_rewrite(int u, int tp){ top[u] = tp, dfn[u] = ++tot, id[tot] = u; if(son[u]) dfs_rewrite(son[u], tp); for(int i = head[u]; ~i; i = e[i].next){ int v = e[i].to; if(v != fa[u] && v != son[u]) dfs_rewrite(v, v); } cnt[v] = tot;//遞歸完子樹後的tot就是dfn最大值 }
而對子樹的操做也就能夠寫得出來了:
inline void getsum_sontree(){//查詢子樹和 int u; scanf("%d", &u); ans = getsum(1, dfn[u], cnt[u]); }
inline void change_sontree(){//修改子樹值 int u, w; scanf("%d %d", &u, &w); change(1, dfn[u], cnt[u], w); }
最後放上代碼(我也不知道用不用long long):
#include <string.h> #include <stdio.h> #define maxn 500010 #define maxm 500010 struct graph{ struct edge{ long long to, next; edge(){} edge(const long long &_to, const long long &_next){ to = _to; next = _next; } }e[maxn << 1]; long long head[maxn], k; inline void init(){ memset(head, -1, sizeof head); k = 0; } inline void add(const long long &u, const long long &v){ e[k] = edge(v, head[u]); head[u] = k++; } }g; struct node{ long long l, r, zuo, you; long long c, f; }t[maxn << 2]; long long fa[maxn], dep[maxn], size[maxn], son[maxn]; long long dfn[maxn], id[maxn], top[maxn], cnt[maxn], tot; long long n, m, root, p, val[maxn], len; inline void swap(long long &x, long long &y){long long t = x; x = y; y = t;} void dfs_getson(long long u){ size[u] = 1; for(long long i = g.head[u]; ~i; i = g.e[i].next){ long long v = g.e[i].to; if(v == fa[u]) continue; fa[v] = u; dep[v] = dep[u] + 1; dfs_getson(v); size[u] += size[v]; if(size[v] > size[son[u]]) son[u] = v; } } void dfs_rewrite(long long u, long long tp){ top[u] = tp; dfn[u] = ++tot; id[tot] = u; if(son[u]) dfs_rewrite(son[u], tp); for(long long i = g.head[u]; ~i; i = g.e[i].next){ long long v = g.e[i].to; if(v != son[u] && v != fa[u]) dfs_rewrite(v, v); } cnt[u] = tot; } void build(long long d, long long l, long long r){ t[d].l = l, t[d].r = r; if(l == r){ t[d].c = val[id[l]]; return; } long long mid = l + r >> 1; build(d << 1, l, mid); build(d << 1 | 1, mid + 1, r); t[d].c = t[d << 1].c + t[d << 1 | 1].c; } inline void down(long long d){ t[d << 1].f += t[d].f; t[d << 1 | 1].f += t[d].f; t[d << 1].c += t[d].f * (t[d << 1].r - t[d << 1].l + 1); t[d << 1 | 1].c += t[d].f * (t[d << 1 | 1].r - t[d << 1 | 1].l + 1); t[d].f = 0; } void change(long long d, long long l, long long r, long long x){ if(l <= t[d].l && t[d].r <= r){ t[d].c += (t[d].r - t[d].l + 1) * x; t[d].f += x; return; } if(t[d].f) down(d); long long mid = t[d].l + t[d].r >> 1; if(l <= mid) change(d << 1, l, r, x); if(r > mid) change(d << 1 | 1, l, r, x); t[d].c = t[d << 1].c + t[d << 1 | 1].c; } long long getsum(long long d, long long l, long long r){ if(l <= t[d].l && t[d].r <= r) return t[d].c; if(t[d].f) down(d); long long mid = t[d].l + t[d].r >> 1; long long ans = 0; if(l <= mid) ans = (ans + getsum(d << 1, l, r)) % p; if(r > mid) ans = (ans + getsum(d << 1 | 1, l, r)) % p; return ans; } inline void change_xtoy(){ long long x, y, z; scanf("%lld%lld%lld",&x, &y, &z); while(top[x] != top[y]) { if(dep[top[x]] > dep[top[y]]) swap(x, y); change(1, dfn[top[y]], dfn[y], z); y = fa[top[y]]; } if(dep[x] > dep[y]) swap(x, y); change(1, dfn[x], dfn[y], z); } inline void getson_xtoy(){ long long x, y; scanf("%lld%lld", &x, &y); long long ans = 0; while(top[x] != top[y]){ if(dep[top[x]] > dep[top[y]]) swap(x, y); ans = (ans + getsum(1, dfn[top[y]], dfn[y])) % p; y = fa[top[y]]; } if(dep[x] > dep[y]) swap(x, y); ans += getsum(1, dfn[x], dfn[y]); printf("%lld\n", ans % p); } inline void change_sontree(){ long long x, y; scanf("%lld%lld", &x, &y); change(1, dfn[x], cnt[x], y); } inline void getsum_sontree(){ long long x; scanf("%lld", &x); printf("%lld\n", getsum(1, dfn[x], cnt[x]) % p); } int main(){ g.init(); scanf("%lld%lld%lld%lld", &n, &m, &root, &p); for(long long i = 1; i <= n; i++) scanf("%lld", &val[i]); for(long long i = 1; i < n; i++){ long long u, v; scanf("%lld%lld", &u, &v); g.add(u, v); g.add(v, u); } dfs_getson(root); dfs_rewrite(root, root); build(1, 1, tot); for(long long i = 1; i <= m; i++){ long long op; scanf("%lld", &op); if(op == 1) change_xtoy(); if(op == 2) getson_xtoy(); if(op == 3) change_sontree(); if(op == 4) getsum_sontree(); } return 0; }
註釋應該就不須要了吧,自我感受代碼仍是能被看懂的(溜~)
Linux用戶和OSX用戶必定對軟件包管理器不會陌生。經過軟件包管理器,你能夠經過一行命令安裝某一個軟件包,而後軟件包管理器會幫助你從軟件源下載軟件包,同時自動解決全部的依賴(即下載安裝這個軟件包的安裝所依賴的其它軟件包),完成全部的配置。Debian/Ubuntu使用的apt-get,Fedora/CentOS使用的yum,以及OSX下可用的homebrew都是優秀的軟件包管理器。
你決定設計你本身的軟件包管理器。不可避免地,你要解決軟件包之間的依賴問題。若是軟件包A依賴軟件包B,那麼安裝軟件包A之前,必須先安裝軟件包B。同時,若是想要卸載軟件包B,則必須卸載軟件包A。如今你已經得到了全部的軟件包之間的依賴關係。並且,因爲你以前的工做,除0號軟件包之外,在你的管理器當中的軟件包都會依賴一個且僅一個軟件包,而0號軟件包不依賴任何一個軟件包。依賴關係不存在環(如有m(m≥2)個軟件包A1,A2,A3,⋯,Am,其中A1依賴A2,A2依賴A3,A3依賴A4,……,A[m-1]依賴Am,而Am依賴A1,則稱這m個軟件包的依賴關係構成環),固然也不會有一個軟件包依賴本身。
如今你要爲你的軟件包管理器寫一個依賴解決程序。根據反饋,用戶但願在安裝和卸載某個軟件包時,快速地知道這個操做實際上會改變多少個軟件包的安裝狀態(即安裝操做會安裝多少個未安裝的軟件包,或卸載操做會卸載多少個已安裝的軟件包),你的任務就是實現這個部分。注意,安裝一個已安裝的軟件包,或卸載一個未安裝的軟件包,都不會改變任何軟件包的安裝狀態,即在此狀況下,改變安裝狀態的軟件包數爲0。
輸入格式:
從文件manager.in中讀入數據。
輸入文件的第1行包含1個整數n,表示軟件包的總數。軟件包從0開始編號。
隨後一行包含n−1個整數,相鄰整數之間用單個空格隔開,分別表示1,2,3,⋯,n−2,n−1號軟件包依賴的軟件包的編號。
接下來一行包含1個整數q,表示詢問的總數。以後q行,每行1個詢問。詢問分爲兩種:
install x:表示安裝軟件包x
uninstall x:表示卸載軟件包x
你須要維護每一個軟件包的安裝狀態,一開始全部的軟件包都處於未安裝狀態。
對於每一個操做,你須要輸出這步操做會改變多少個軟件包的安裝狀態,隨後應用這個操做(即改變你維護的安裝狀態)。
輸出格式:
輸出到文件manager.out中。
輸出文件包括q行。
輸出文件的第i行輸出1個整數,爲第i步操做中改變安裝狀態的軟件包數。
這題有點像上面那道模板題。這題其實已經暗示咱們不少了。首先,0號軟件包不依賴任何軟件包——這不就是說0爲根節點嗎?其次,依賴關係——這不就是讓咱們把u向着依賴它的v連一條邊嗎?因而咱們就建好了一顆樹。若是咱們要安裝軟件包A,由題意得,咱們須要將u到根節點路上全部爲安裝的軟件包都安裝上;而刪除u呢?又由題意得,咱們須要刪掉全部依賴u的軟件包,而依賴那些軟件包的軟件包也得被刪掉。這不就是上一題寫過的對子樹的操做嗎?因此咱們只須要再用線段樹維護一下區間上已安裝的軟件包個數,這個題就也被切掉了。
#include <iostream> #include <cstring> #include <cstdio> #define maxn 100010 #define maxm 100010 using namespace std; struct edge{ int to, next; edge(){} edge(const int &_to, const int &_next){ to = _to; next = _next; } }e[maxn << 1]; int head[maxn], k; struct node{ int l, r, c, f; }t[maxn << 2]; int size[maxn], fa[maxn], dep[maxn], son[maxn]; int dfn[maxn], id[maxn], top[maxn], cnt[maxn], tot; int n, m; inline void add(const int &u, const int &v){ e[k] = edge(v, head[u]); head[u] = k++; } void dfs_getson(int u){ size[u] = 1; for(int i = head[u]; ~i; i = e[i].next){ int v = e[i].to; if(v == fa[u]) continue; fa[v] = u, dep[v] = dep[u] + 1; dfs_getson(v); size[u] += size[v]; if(size[v] > size[son[u]]) son[u] = v; } } inline void dfs_rewrite(int u, int tp){ top[u] = tp, dfn[u] = ++tot, id[tot] = u; if(son[u]) dfs_rewrite(son[u], tp); for(int i = head[u]; ~i; i = e[i].next){ int v = e[i].to; if(v != fa[u] && v != son[u]) dfs_rewrite(v, v); } cnt[u] = tot; } void build(int d, int l, int r){ t[d].l = l, t[d].r = r; if(l == r) return; int mid = l + r >> 1; build(d << 1, l, mid), build(d << 1 | 1, mid + 1, r); } inline void down(int d){ if(t[d].f == 2){ t[d << 1].c = t[d << 1].r - t[d << 1].l + 1; t[d << 1 | 1].c = t[d << 1 | 1].r - t[d << 1 | 1].l + 1; t[d << 1].f = t[d << 1 | 1].f = t[d].f; }else if(t[d].f == 1){ t[d << 1].c = t[d << 1 | 1].c = 0; t[d << 1].f = t[d << 1 | 1].f = t[d].f; } t[d].f = 0; } int change(int d, const int &l, const int &r, const int &op){ if(l <= t[d].l && t[d].r <= r){ int ans = t[d].c; if(op == 2) t[d].c = t[d].r - t[d].l + 1; else t[d].c = 0; t[d].f = op; return ans; } if(op) down(d); int mid = t[d].l + t[d].r >> 1, ans = 0; if(l <= mid) ans += change(d << 1, l, r, op); if(r > mid) ans += change(d << 1 | 1, l, r, op); t[d].c = t[d << 1].c + t[d << 1 | 1].c; return ans; } inline void change_path(); inline void change_sontree(); int main(){ memset(head, -1, sizeof head); scanf("%d", &n); for(int i = 2; i <= n; i++){ int v; scanf("%d", &v);v++; add(i, v), add(v, i); } dfs_getson(1); dfs_rewrite(1, 1); build(1, 1, tot); scanf("%d", &m); while(m--){ string op; cin >> op; if(op == "install") change_path(); else change_sontree(); } return 0; } inline void change_path(){ int u, ans; scanf("%d", &u);u++; ans = dep[u] - dep[1] + 1; while(top[u] != 1){ ans -= change(1, dfn[top[u]], dfn[u], 2);//被刪掉的個數就等於節點總數減被安裝了的軟件包個數 u = fa[top[u]]; } ans -= change(1, 1, dfn[u], 2); printf("%d\n", ans); } inline void change_sontree(){ int u, ans; scanf("%d", &u);u++; ans = change(1, dfn[u], cnt[u], 1); printf("%d\n", ans); }
這裏的節點編號要加1。注意這不是習慣!爲何呢?首先,咱們的son數組一開始都是0,若不給節點編號加1的話,第二次DFS時就可能出錯。
這是一道很好的樹剖題(至少不是模板)。很直接地,咱們會想到用線段樹記下區間內的顏色段的數量。因而咱們遞歸下去,若是到了所要改的區間,咱們就把區間的顏色段數量改爲1就行了。但回溯時,若左兒子的右端點顏色和右兒子的左端點顏色同樣,怎麼判斷呢?因而咱們須要再記下兩個信息:區間左端點的顏色和右端點的顏色。若左兒子的右端點顏色和右兒子的左端點顏色同樣,將兩個兒子的顏色段數加起來後再減1即
可。就是代碼有點難調,個人那個BUG代碼還沒調好(果真太弱了)。
一棵樹上有n個節點,編號分別爲1到n,每一個節點都有一個權值w。
咱們將如下面的形式來要求你對這棵樹完成一些操做:
I. CHANGE u t : 把結點u的權值改成t
II. QMAX u v: 詢問從點u到點v的路徑上的節點的最大權值
III. QSUM u v: 詢問從點u到點v的路徑上的節點的權值和
注意:從點u到點v的路徑上的節點包括u和v自己
輸入格式:
輸入文件的第一行爲一個整數n,表示節點的個數。
接下來n – 1行,每行2個整數a和b,表示節點a和節點b之間有一條邊相連。
接下來一行n個整數,第i個整數wi表示節點i的權值。
接下來1行,爲一個整數q,表示操做的總數。
接下來q行,每行一個操做,以「CHANGE u t」或者「QMAX u v」或者「QSUM u v」的形式給出。
輸出格式:
對於每一個「QMAX」或者「QSUM」的操做,每行輸出一個整數表示要求輸出的結果。
真正的模板題。若是你以爲你寫樹剖還不熟練,能夠拿這題練(shui)練(jing)手(yan)。話說樹剖的代碼真的難調。
給定一棵n個節點的樹,有兩個操做:
CHANGE i ti 把第i條邊的邊權變成ti
QUERY a b 輸出從a到b的路徑中最大的邊權,當a=b的時候,輸出0
輸入格式:
第一行輸入一個n,表示節點個數
第二行到第n行每行輸入三個數,ui,vi,wi,分別表示 ui,vi有一條邊,邊權是wi
第n+1行開始,一共有不定數量行,每一行分別有如下三種可能
CHANGE,QUERY同題意所述
DONE表示輸入結束
輸出格式:
對於每一個QUERY操做,輸出一個數,表示a b之間邊權最大值
這道題的技巧在不少樹剖題都會用到~咱們樹剖時,計算的都是點權對不對?但是這題就不一樣些,給的是邊權。這時怎麼辦呢?就用到了一種技巧,叫.......我也不知道叫什麼。就是把邊權轉化爲點權。怎麼轉呢?假若有邊(u,v),若是咱們把邊權存在深度大的那個節點上去,也就是在節點x存下x連向父親節點的邊的權值。這樣有什麼用呢?想一想,除了根節點,每一個節點都惟一地只有一個父親對吧,因此這樣存不就把n-1條邊的權值存在n-1個點上了嗎?而那個沒存的就是根節點了。因而,計算邊權的題就能夠轉化爲計算點權的題了。不過這樣作還須要注意一個事項,因爲每一個點存的都是連向父親邊的邊權,因此咱們在計算樹上兩點之間的信息時,這兩點的lca是不可用的(我不會告訴你我由於這個寫爆過)。
例題就講到這裏吧。。。
其實樹剖還能夠作lca。現有樹上兩點u,v,若求他們的lca,就樹剖後一直跳鏈,直到top相同爲止。此時深度小的那個就是lca了~也就是說如今咱們有三種求lca的方法了:樹上倍增、Tarjan和樹剖(還有向上標記)。咱們來對比一下。其實三種算法的思路都是同樣,一直往上跳便可。而樹上倍增時跳的是2的k次方,雖然很大,但仍是沒有樹剖跳一跳鏈跳得完全。而樹剖跳的時候,若是鏈多了,仍是會費時的。但Tarjan就異常強大了,在遍歷圖的過程當中遇到了能夠回答的詢問就能得出答案,你能夠理解爲如今遍歷到了u,而詢問裏有求lca(u,v)而且v已經遍歷過,此時lca就是v在並查集的生成樹裏的祖先,也就是直接把u,v直接拽到了lca的位置去,跳都不跳了。但畢竟是離線算法,費空間......再對比一下時間複雜度,樹上倍增爲O((N+M)logN),樹剖爲(N+MlogN),Tarjan爲O(N+M),Tarjan最優。而空間複雜度呢?樹上倍增法爲O(NlogN),樹剖爲O(N),Tarjan爲O(N+M)(詢問與並查集),樹剖最優。綜上,我的認爲樹剖仍是最好的求lca的算法(雖然有點小題大作而且暴力得一批)。因此大家能夠嘗試作一下lca的題: