【題解】SDOI2017樹點塗色
LCT強強!以前總是覺得LCT非常的難懂(當然現在也是的),但實際上它真的是很厲害的一種東西。它是一種動態的鏈剖分結構,其實就是對於剖分出來的重鏈使用LCT去進行維護。cut 與 link 兩個操作讓我們可以構造出希望存在的鏈(動態更新),而 split 操作則可以提取出任意一條從 \(u\) 到 \(v\) 的鏈使得這條鏈成為重鏈,也就是處於一棵 splay 當中,並用根節點來返回信息。可以說,大部分與鏈有關的問題都可以考慮使用LCT來求解。
那麽這道題乍一看1操作十分的棘手,但如果和LCT聯想的話會發現實際上這就是一個 access 的操作,讓一個節點到根的路徑成為重鏈並斷開原有的重兒子。這樣一個條路徑上的顏色個數,就是經過的虛邊的條數。對於操作2,實際上是一個 LCT 的模板應用。我們維護顏色段的信息,用 \(LC[u]\) 和 \(RC[u]\) 分別代表以\(u\) 為根節點在 splay 上的子樹的最右和最左兩個節點的顏色。這樣就可以維護了。修改顏色使用一個標記即可。
而3操作我們可以發現:在 access 的時候,我們每修改一條虛邊為實邊,就會斷開一條原本為實邊的邊為虛邊。這樣對於它們子樹的貢獻分別是 \(-1, +1\)。我們在線段樹上維護一下加減值即可。不過要註意由於3操作中的 access 已經用於維護樹的形態了,我們就不能再隨意的去變動它。所以為了實現2和3操作,我們必須使用兩棵LCT。以及在翻轉子樹的時候,\(LC[u]\) 和 \(RC[u]\) 都必須要翻轉!
這裏我也有一個不是很理解的地方。標記一般來說有兩種,一種打了標記表示自身及子樹還未被修改,另一種則表示自身已經被修改,子樹還未被修改。在以往我寫的 LCT 中,翻轉標記用第一種來維護完全沒有問題。可以加入了覆蓋的標記之後,只有第2種才是正確的。我也不是很懂為什麽……如果有知道的,還請評論 \ 私信我一下好嗎QAQ
加之以前我一直以為LCT只能維護鏈信息,但老師說也是可以維護子樹信息的,只要在添加虛邊的時候更新一下即可,但這個必須要子樹貢獻滿足可加性才行。
#include <bits/stdc++.h> using namespace std; #define maxn 1000000 int n, m, timer, size[maxn]; int id[maxn], dfn[maxn], dep[maxn]; int read() { int x = 0, k = 1; char c; c = getchar(); while(c < ‘0‘ || c > ‘9‘) { if(c == ‘-‘) k = -1; c = getchar(); } while(c >= ‘0‘ && c <= ‘9‘) x = x * 10 + c - ‘0‘, c = getchar(); return x * k; } struct edge { int cnp, to[maxn], last[maxn], head[maxn]; edge() { cnp = 1; } void add(int u, int v) { to[cnp] = v, last[cnp] = head[u], head[u] = cnp ++; to[cnp] = u, last[cnp] = head[v], head[v] = cnp ++; } }E1; struct Segament_Tree { int mark[maxn], mx[maxn]; void Build(int p, int l, int r) { if(l == r) { mx[p] = dep[id[l]]; return; } int mid = (l + r) >> 1; Build(p << 1, l, mid), Build(p << 1 | 1, mid + 1, r); mx[p] = max(mx[p << 1], mx[p << 1 | 1]); } void push_down(int u) { if(!mark[u]) return; mark[u << 1] += mark[u]; mark[u << 1 | 1] += mark[u]; mx[u << 1] += mark[u], mx[u << 1 | 1] += mark[u]; mark[u] = 0; } void update(int p, int l, int r, int L, int R, int x) { if(L <= l && R >= r) { mark[p] += x; mx[p] += x; return; } if(L > r || R < l) return; int mid = (l + r) >> 1; push_down(p); update(p << 1, l, mid, L, R, x); update(p << 1 | 1, mid + 1, r, L, R, x); mx[p] = max(mx[p << 1], mx[p << 1 | 1]); } int query(int p, int l, int r, int L, int R) { if(L <= l && R >= r) return mx[p]; if(L > r || R < l) return 0; push_down(p); int mid = (l + r) >> 1; return max(query(p << 1, l, mid, L, R), query(p << 1 | 1, mid + 1, r, L, R)); } }ST; struct Link_Cut_Tree { int fa[maxn], rev[maxn], ch[maxn][2]; int sum[maxn], LC[maxn], RC[maxn], col[maxn]; int tot, flag, mark[maxn], top[maxn]; bool is_root(int u) { return (ch[fa[u]][1] != u) && ( ch[fa[u]][0] != u ); } void Modify(int u, int x) { if(!u) return; mark[u] = x; LC[u] = RC[u] = col[u] = x, sum[u] = 1; } void Rev(int u) { if(!u) return; rev[u] ^= 1; swap(ch[u][0], ch[u][1]); swap(LC[u], RC[u]); } void push_down(int u) { if(!is_root(u)) push_down(fa[u]); if(rev[u]) Rev(ch[u][0]), Rev(ch[u][1]), rev[u] = 0; if(mark[u]) Modify(ch[u][0], mark[u]), Modify(ch[u][1], mark[u]), mark[u] = 0; } void update(int u) { int lc = ch[u][0], rc = ch[u][1]; if(flag) { sum[u] = sum[lc] + sum[rc] + ((col[u] != RC[lc]) && (col[u] != LC[rc])); sum[u] -= ((col[u] == RC[lc]) && (col[u] == LC[rc])); LC[u] = LC[lc], RC[u] = RC[rc]; if(!lc) LC[u] = col[u]; if(!rc) RC[u] = col[u]; } else top[u] = top[lc] ? top[lc] : u; } void rotate(int u) { int f = fa[u], gf = fa[f]; int k = ch[f][1] == u; fa[u] = gf; if(!is_root(f)) ch[gf][ch[gf][1] == f] = u; ch[f][k] = ch[u][k ^ 1], fa[ch[u][k ^ 1]] = f; fa[f] = u, ch[u][k ^ 1] = f; update(f), update(u); } void Splay(int u) { push_down(u); while(!is_root(u)) { int f = fa[u], gf = fa[f]; if(!is_root(f)) (ch[gf][1] == f) ^ (ch[f][1] == u) ? rotate(u) : rotate(f); rotate(u); } update(u); } void Access(int u) { for(int i = u, last = 0; i; last = i, i = fa[i]) { Splay(i); if(!flag) { int x = top[last], y = top[ch[i][1]]; if(x) ST.update(1, 1, n, dfn[x], dfn[x] + size[x] - 1, -1); if(y) ST.update(1, 1, n, dfn[y], dfn[y] + size[y] - 1, 1); } ch[i][1] = last; update(i); } } void Make_root(int u) { Access(u); Splay(u); Rev(u); } void Split(int u, int v) { Make_root(u); Access(v); Splay(v); } void Change1(int u) { ++ tot; Access(u); } void Change2(int u) { Split(1, u); Modify(u, ++ tot); } void Link(int u, int v) { Make_root(u); fa[u] = v; } int Query(int u, int v) { Split(u, v); return sum[v]; } }LCT1, LCT2; void dfs(int u, int fa) { LCT1.fa[u] = fa; dfn[u] = ++ timer; size[u] = 1; dep[u] = dep[fa] + 1; id[timer] = u; LCT1.top[u] = u; for(int i = E1.head[u]; i; i = E1.last[i]) { int v = E1.to[i]; if(v == fa) continue; dfs(v, u); size[u] += size[v]; } } int main() { n = read(), m = read(); LCT2.flag = 1; for(int i = 1; i <= n; i ++) LCT2.col[i] = LCT2.RC[i] = LCT2.LC[i] = ++ LCT2.tot, LCT2.sum[i] = 1; for(int i = 1; i < n; i ++) { int x = read(), y = read(); E1.add(x, y); LCT2.Link(x, y); } dfs(1, 0); ST.Build(1, 1, n); for(int i = 1; i <= m; i ++) { int opt = read(); if(opt == 1) { int x = read(); LCT1.Change1(x); LCT2.Change2(x); } else if(opt == 2) { int x = read(), y = read(); printf("%d\n", LCT2.Query(x, y)); } else if(opt == 3) { int x = read(); printf("%d\n", ST.query(1, 1, n, dfn[x], dfn[x] + size[x] - 1)); } } return 0; }
【題解】SDOI2017樹點塗色