luogu3703 [SDOI2017]樹點塗色(線段樹+樹鏈剖分+動態樹)
link
你谷的第一篇題解沒用寫LCT,然後沒觀察懂,但是自己YY了一種不用LCT的做法
我們考慮對於每個點,維護一個fa,代表以1為根時候這個點的父親
再維護一個bel,由於一個顏色相同的段一定是一個深度遞增的鏈,這個代表顏色段的鏈頂
再維護一個ans,就是ans
那麽第二個操作就是ans單點查詢,第三個就是ans區間最大值
第一個操作,我們樹剖把它變成了log條重鏈,在重鏈上的點的bel和ans可以區間修改
但是重鏈上每個點的兒子就說gg了
這些兒子的子樹減去的是他們的各自bel的fa的點權,然後在加上一個1
所以說每個點還要維護一個preferred child,顯然很難寫(但是應該是可以實現的),還是寫動態樹吧
動態樹其實是動態樹的閹割版,我們用一個splay維護樹中顏色相同的點,顯然這種點一定是一段深度遞增的鏈,於是你會發現只需要欽定1為根,操作1就是access
然後我們可以維護每個點操作3的ans,再搞個dfn,操作3就可以用線段樹維護,然而每個點的答案就相當於動態樹上這個點到根節點上虛邊的個數+1,我們可以搞個線段樹維護,在動態樹access虛實變化時候搞個區間加法即可
然後你會發現對於操作2的x,y答案就是ans[x] + ans[y] - 2 * ans[lc] + 1,所以說我們還需要支持一個lca操作,寫個樹剖或者倍增就行了,我為了湊齊全家桶就寫了個樹剖
註意樹剖的fa和lct的fa一開始是一個fa,lct的fa是動態維護的,樹剖的fa是靜態的,要開兩個數組並且別弄混了,調了好長時間...
access過程中由於變換的是深度恰好為depth[x]+1的點,所以就要對x尋找後繼,就是x的右兒子不停往左跳。。。
時間復雜度Nlog^2N
#include <cstdio> #include <vector> using namespace std; //區間加法,區間取max int fa[100010], dfn[100010], depth[100010], weight[100010], wson[100010], top[100010]; int tree[400010], lazy[400010], fuck[100010], ch[100010][2], fat[100010], tot, n, m; vector<int> out[100010]; void init(int x, int cl, int cr) { if (cl == cr) { tree[x] = fuck[cl]; } else { int mid = (cl + cr) / 2; init(x * 2, cl, mid), init(x * 2 + 1, mid + 1, cr); tree[x] = max(tree[x * 2], tree[x * 2 + 1]); } } void pushdown(int x) { tree[x * 2] += lazy[x], tree[x * 2 + 1] += lazy[x]; lazy[x * 2] += lazy[x], lazy[x * 2 + 1] += lazy[x]; lazy[x] = 0; } void chenge(int x, int cl, int cr, int L, int R, int val) { if (cr < L || R < cl) return; if (L <= cl && cr <= R) { tree[x] += val, lazy[x] += val; return; } pushdown(x); int mid = (cl + cr) / 2; chenge(x * 2, cl, mid, L, R, val); chenge(x * 2 + 1, mid + 1, cr, L, R, val); tree[x] = max(tree[x * 2], tree[x * 2 + 1]); } int query(int x, int cl, int cr, int L, int R) { if (cr < L || R < cl) return 0; if (L <= cl && cr <= R) return tree[x]; pushdown(x); int mid = (cl + cr) / 2; return max(query(x * 2, cl, mid, L, R), query(x * 2 + 1, mid + 1, cr, L, R)); } void dfs1(int x) { weight[x] = 1, wson[x] = -1; for (int i : out[x]) if (fa[x] != i) { fa[i] = fat[i] = x, depth[i] = depth[x] + 1; dfs1(i), weight[x] += weight[i]; if (wson[x] == -1 || weight[wson[x]] < weight[i]) wson[x] = i; } } void dfs2(int x, int topf) { dfn[x] = ++tot, top[x] = topf, fuck[dfn[x]] = depth[x] + 1; if (wson[x] == -1) return; dfs2(wson[x], topf); for (int i : out[x]) if (fa[x] != i && wson[x] != i) dfs2(i, i); } int lca(int x, int y) { while (top[x] != top[y]) { if (depth[top[x]] < depth[top[y]]) swap(x, y); x = fa[top[x]]; } if (depth[x] > depth[y]) swap(x, y); return x; } bool nroot(int x) { return ch[fat[x]][0] == x || ch[fat[x]][1] == x; } void rotate(int x) { int y = fat[x], z = fat[y], k = ch[y][1] == x, w = ch[x][k ^ 1]; if (nroot(y)) { ch[z][ch[z][1] == y] = x; } ch[x][k ^ 1] = y, ch[y][k] = w; if (w) { fat[w] = y; } fat[y] = x; fat[x] = z; } void splay(int x) { while (nroot(x)) { int y = fat[x], z = fat[y]; if (nroot(y)) rotate((ch[y][1] == x) ^ (ch[z][1] == y) ? x : y); rotate(x); } } int findrt(int x) { while (ch[x][0]) x = ch[x][0]; return x; } void access(int x) { for (int y = 0; x > 0; x = fat[y = x]) { splay(x); if (ch[x][1]) { int p = findrt(ch[x][1]); chenge(1, 1, n, dfn[p], dfn[p] + weight[p] - 1, 1); } ch[x][1] = y; if (y) { int p = findrt(y); chenge(1, 1, n, dfn[p], dfn[p] + weight[p] - 1, -1); } } } int main() { scanf("%d%d", &n, &m); for (int x, y, i = 1; i < n; i++) scanf("%d%d", &x, &y), out[x].push_back(y), out[y].push_back(x); dfs1(1), dfs2(1, 1); init(1, 1, n); for (int opd, x, y, i = 1; i <= m; i++) { scanf("%d%d", &opd, &x); if (opd == 1) access(x); if (opd == 2) { scanf("%d", &y); int lc = lca(x, y); printf("%d\n", query(1, 1, n, dfn[x], dfn[x]) + query(1, 1, n, dfn[y], dfn[y]) - 2 * query(1, 1, n, dfn[lc], dfn[lc]) + 1); } if (opd == 3) printf("%d\n", query(1, 1, n, dfn[x], dfn[x] + weight[x] - 1)); } return 0; }
luogu3703 [SDOI2017]樹點塗色(線段樹+樹鏈剖分+動態樹)