線段樹 \(and\) 樹上基本操做ios
幾個在樹鏈剖分很重要的概念。數據結構
對於一個父節點,含有節點數最多的兒子稱爲重兒子。但重兒子只有一個,若知足條件的兒子有多個,則指定其中任意一個兒子爲重兒子。ui
對於一個父節點,除了重兒子覺得,其他的都稱爲輕兒子。spa
由父節點與重兒子構成的邊。code
由父節點與輕兒子構成的邊。blog
由重邊構成的鏈。圖片
由輕邊構成的鏈。ci
重鏈中深度最小的邊爲該重鏈的鏈頂。get
上述幾個概念具體以下圖:string
其中,黃色點爲重兒子,藍色點爲輕兒子( \(1\) 除外)。黃色邊爲重邊,藍色邊爲輕邊。一般,一個單獨的點也看爲一條重鏈,那麼重鏈有 \(5\) 條:
對於任意一棵樹有以下性質:從任意一點到根節點的簡單路徑上,共有不超過 \(log2(n)\) 條輕鏈,有不超過 \(log2(n)\) 條輕鏈。
預處理須要使用到兩個 \(dfs\) 。
\(dfs1\) 須要處理:
\(dfs2\) 須要處理:
void dfs1(int now, int father) { fa[now] = father;//初始化父節點 sz[now] = 1;//子樹大小包括本身 dep[now] = dep[father] + 1;//初始化深度 int SIZ = v[now].size(); int maxn = 0;//記錄最大的子樹大小 for(int i = 0; i < SIZ; i++) { int next = v[now][i]; if(next == father) continue; dfs1(next, now);//遍歷這棵樹 sz[now] += sz[next]; if(maxn < sz[next]) {//更新子節點 maxn = sz[next]; son[now] = next; } } } void dfs2(int now, int Top) { tp[now] = Top;//初始化鏈頂 if(son[now]) dfs2(son[now], Top);//優先遍歷重兒子 int SIZ = v[now].size(); for(int i = 0; i < SIZ; i++) { int next = v[now][i]; if(next == fa[now] || next == son[now]) continue; dfs2(next, next);//繼續遍歷這棵樹 } }
在線查詢一棵樹上任意兩點的 \(LCA\) 。
分兩種狀況向上爬便可(須要保證 \(dep[tp[x]] <= dep[tp[y]]\)):
正確性顯然,覺得兩條重鏈不會交於同一個點。
#include <cstdio> #include <vector> using namespace std; const int MAXN = 5e5 + 5; vector<int> v[MAXN];//vector存圖 int fa[MAXN], son[MAXN], tp[MAXN], sz[MAXN], dep[MAXN]; int n, m, s; void dfs1(int now, int father) {//初始化如上 fa[now] = father; sz[now] = 1; dep[now] = dep[father] + 1; int SIZ = v[now].size(); int maxn = 0; for(int i = 0; i < SIZ; i++) { int next = v[now][i]; if(next == father) continue; dfs1(next, now); sz[now] += sz[next]; if(maxn < sz[next]) { maxn = sz[next]; son[now] = next; } } } void dfs2(int now, int Top) {//初始化如上 tp[now] = Top; if(son[now]) dfs2(son[now], Top); int SIZ = v[now].size(); for(int i = 0; i < SIZ; i++) { int next = v[now][i]; if(next == fa[now] || next == son[now]) continue; dfs2(next, next); } } int Get_LCA(int x, int y) { while(tp[x] != tp[y]) { if(dep[tp[x]] < dep[tp[y]]) swap(x, y); x = fa[tp[x]]; } if(x == y) return x; if(dep[x] < dep[y]) return x; return y; } int main() { int A, B; scanf("%d %d %d", &n, &m, &s); for(int i = 1; i < n; i++) { scanf("%d %d", &A, &B); v[A].push_back(B); v[B].push_back(A); } dfs1(s, 0); dfs2(s, s); for(int i = 1; i <= m; i++) { scanf("%d %d", &A, &B); printf("%d\n", Get_LCA(A, B)); } return 0; }
樹鏈剖分一般結合着一些數據結構來進行操做,覺得重鏈的 \(dfn\) 爲連續的序列。
對於一棵樹,有 \(5\) 中操做,根據要求完成操做。
先考慮第二個操做。
設 \(lca\) 爲 \(u,v\) 的最近公共祖先,那麼能夠將操做二分解爲從 \(u\) 到 \(lca\) 的路徑取反,和將從 \(v\) 到 \(lca\) 的路徑取反。
那麼按照上述 \(LCA\) 往上爬的過程恰好就能夠遍歷完這條路徑一次。
按照點的 \(dfn\) 建造一顆線段樹,來維護點的信息。
這裏點的信息是指:這個點與它的父節點的連邊的信息。
線段樹須要維護的信息有:最大值,最小值,區間和。
具體的操做二代碼以下:
void Negate(int pos, int l, int r) { if(l <= L(pos) && R(pos) <= r) { A(pos) = -A(pos); I(pos) = -I(pos); swap(A(pos), I(pos));//最大值取反,最小值取反,最大值邊最小值 S(pos) = -S(pos);//區間和取反 M(pos) ^= 1;//取反兩次後就至關於不取反。 return; } 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) { while(tp[x] != tp[y]) {//不一樣重鏈向上爬 if(dep[tp[x]] < dep[tp[y]]) swap(x, y); Negate(1, dfn[tp[x]], dfn[x]);//同一條重鏈dfn是連續的,線段樹維護 x = fa[tp[x]];//向上爬 } if(x == y) return; if(dep[x] < dep[y]) swap(x, y); Negate(1, dfn[son[y]], dfn[x]);//注意是son[y],y與其父節點的連邊並不須要修改 }
查詢操做與其相似,就不一一列舉了。
對於修改權值,使用深度較大的子節點,直接在線段樹上該就行了。
#include <cstdio> #include <string> #include <vector> #include <iostream> #include <algorithm> using namespace std; #define INF 0x3f3f3f3f const int MAXN = 2e5 + 5; struct Segment_Tree {//線段樹 int Left_Section, Right_Section; int Max_Data, Min_Data, Sum_Data, Lazy_Mark; #define LC(x) (x << 1) #define RC(x) (x << 1 | 1) #define L(x) Tree[x].Left_Section//左區間 #define R(x) Tree[x].Right_Section//右區間 #define I(x) Tree[x].Min_Data//區間最小值 #define A(x) Tree[x].Max_Data//區間最大值 #define S(x) Tree[x].Sum_Data//區間和 #define M(x) Tree[x].Lazy_Mark//懶惰標記(延遲標記) }; Segment_Tree Tree[MAXN << 2]; vector<int> v[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) {//更新節點信息 A(pos) = max(A(LC(pos)), A(RC(pos))); I(pos) = min(I(LC(pos)), I(RC(pos))); S(pos) = S(LC(pos)) + S(RC(pos)); } void Push_Down(int pos) {//傳遞懶標記 if(M(pos)) { I(LC(pos)) = -I(LC(pos)); A(LC(pos)) = -A(LC(pos)); swap(I(LC(pos)), A(LC(pos))); S(LC(pos)) = -S(LC(pos)); M(LC(pos)) ^= 1; I(RC(pos)) = -I(RC(pos)); A(RC(pos)) = -A(RC(pos)); swap(I(RC(pos)), A(RC(pos))); S(RC(pos)) = -S(RC(pos)); M(RC(pos)) ^= 1; M(pos) = 0; } } void Build(int pos, int l, int r) {//初始化建樹 L(pos) = l; R(pos) = r; if(l == r) return; int mid = (l + r) >> 1; Build(LC(pos), l, mid); Build(RC(pos), mid + 1, r); } void Negate(int pos, int l, int r) {//取反操做 if(l <= L(pos) && R(pos) <= r) { I(pos) = -I(pos); A(pos) = -A(pos); swap(I(pos), A(pos)); S(pos) = -S(pos); M(pos) ^= 1; return; } 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) { while(tp[x] != tp[y]) { if(dep[tp[x]] < dep[tp[y]]) swap(x, y); Negate(1, dfn[tp[x]], dfn[x]); x = fa[tp[x]]; } 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) {//單點修改 if(L(pos) == R(pos)) { I(pos) = c; A(pos) = c; S(pos) = c; return; } 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) {//查詢最大值操做,與修改相似,都已一樣方向爬 if(l <= L(pos) && R(pos) <= r) return S(pos); Push_Down(pos); int res = 0; if(l <= R(LC(pos))) res += Query_Sum(LC(pos), l, r); if(r >= L(RC(pos))) res += Query_Sum(RC(pos), l, r); return res; } int Sumpast(int x, int y) { int res = 0; while(tp[x] != tp[y]) { if(dep[tp[x]] < dep[tp[y]]) swap(x, y); res += Query_Sum(1, dfn[tp[x]], dfn[x]); x = fa[tp[x]]; } 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) {//查詢最小值 if(l <= L(pos) && R(pos) <= r) return I(pos); Push_Down(pos); int res = INF; if(l <= R(LC(pos))) res = min(res, Query_Min(LC(pos), l, r)); if(r >= L(RC(pos))) res = min(res, Query_Min(RC(pos), l, r)); return res; } int Minpast(int x, int y) { int res = INF; while(tp[x] != tp[y]) { if(dep[tp[x]] < dep[tp[y]]) swap(x, y); res = min(res, Query_Min(1, dfn[tp[x]], dfn[x])); x = fa[tp[x]]; } 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) {//查詢最大值 if(l <= L(pos) && R(pos) <= r) return A(pos); Push_Down(pos); int res = -INF; if(l <= R(LC(pos))) res = max(res, Query_Max(LC(pos), l, r)); if(r >= L(RC(pos))) res = max(res, Query_Max(RC(pos), l, r)); return res; } int Maxpast(int x, int y) { int res = -INF; while(tp[x] != tp[y]) { if(dep[tp[x]] < dep[tp[y]]) swap(x, y); res = max(res, Query_Max(1, dfn[tp[x]], dfn[x])); x = fa[tp[x]]; } 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) {//初始化 dep[now] = dep[father] + 1; siz[now] = 1; fa[now] = father; int SIZ = v[now].size(); int maxn = 0; for(int i = 0; i < SIZ; i++) { int next = v[now][i]; if(next == fa[now]) continue; dfs1(next, now); siz[now] += siz[next]; if(maxn < siz[next]) { maxn = siz[next]; son[now] = next; } } } void dfs2(int now, int Top) {//初始化 tp[now] = Top; dfn[now] = ++tim; if(son[now]) dfs2(son[now], Top); int SIZ = v[now].size(); for(int i = 0; i < SIZ; i++) { int next = v[now][i]; if(son[now] == next || fa[now] == next) continue; dfs2(next, next); } } int main() { scanf("%d", &n); for(int i = 1; i < n; i++) { scanf("%d %d %d", &s[i], &t[i], &w[i]); s[i]++; t[i]++; v[s[i]].push_back(t[i]); v[t[i]].push_back(s[i]); } dfs1(1, 0); dfs2(1, 1); Build(1, 1, n); for(int i = 1; i < n; i++) { if(dep[s[i]] < dep[t[i]])//深度小的必定就是子節點 swap(s[i], t[i]); Change(1, dfn[s[i]], w[i]);//初始化線段樹 } scanf("%d", &q); string opt; int a, b; while(q--) { cin >> opt; scanf("%d %d", &a, &b); a++; b++; if(opt[0] == 'N') Negatepast(a, b); else if(opt[0] == 'C') Change(1, dfn[s[a - 1]], b - 1); else if(opt[0] == 'S') printf("%d\n", Sumpast(a, b)); else if(opt[1] == 'A') printf("%d\n", Maxpast(a, b)); else printf("%d\n", Minpast(a, b)); } return 0; }