【luogu CF1137F】Matches Are Not a Child‘s Play
阿新 • • 發佈:2021-06-14
Matches Are Not a Child's Play
題目連結:luogu CF1137F
題目大意
定義一個樹的序列是每次把權值最小葉節點刪去,這個刪去的順序序列。
然後給你一個樹,要你維護三個操作:
把一個點的權值改成當前樹最大權值+1,求一個點在這個序列中的位置,比較兩個點在這個序列中誰更靠前。
思路
易得第三個問題是來搞笑的,搞出第二個問題就行。
首先我們考慮求出了一開始的刪除序列(這個好求),然後進行修改會怎麼變動。
那你會發現搞到最後會剩下一條鏈,就是最大和第二大為兩端的鏈。
然後對於這條鏈,它會從第二大那一段開始刪,一直刪,刪到最大那邊。
那你發現這段有序,考慮把這一條鏈提取出來,用 LCT。
那你考慮把它染成最深的顏色。
至於怎麼染色,就是搞一個懶標記,它的顏色傳給它的兒子。
那我們想到就分出了虛實邊,實邊是顏色相同的,虛邊是顏色不同的。
它看似要提取鏈,但其實我們不用,一開始我們建樹以 \(n\) 為根,接著每次就把要修改的點 \(i\) 弄成根,那每次到根節點的 access 路徑就是我們要的路徑了。
那我們考慮對於一個點的刪除序列中位置,就是權值比它小的個數加上它所在實鏈上顏色比它深的個數加一。
那對於求權值比它小的,我們可以用一個樹狀陣列來搞。
那我們 access 修改的時候,我們就要先維護一個 \(sz\) 代表它實子樹大小,然後每次減去之前的顏色,加上新的顏色。
然後置於它所在實鏈上顏色比它深的,我們就直接反應為在平衡樹上它右子樹的個數。
然後就可以了,具體的實現可以看看程式碼。
程式碼
#include<cstdio> #include<algorithm> using namespace std; struct node { int to, nxt; }e[400001]; int n, q, l[200001], r[200001], tot; int x, y, fa[200001], sz[200001]; int tree[400001], col[200001]; int le[200001], KK; bool lzs[200001]; char op; void add(int x, int y) { e[++KK] = (node){y, le[x]}; le[x] = KK; e[++KK] = (node){x, le[y]}; le[y] = KK; } //樹狀陣列 void add_(int x, int y) { for (; x <= n + q; x += x & (-x))//記得要預留好新開的顏色位置 tree[x] += y; } int query_(int x) { int re = 0; for (; x; x -= x & (-x)) re += tree[x]; return re; } //LCT bool nrt(int now) { return l[fa[now]] == now || r[fa[now]] == now; } bool ls(int now) { return l[fa[now]] == now; } void up(int now) { sz[now] = sz[l[now]] + sz[r[now]] + 1; } void downs(int now) { lzs[now] ^= 1; swap(l[now], r[now]); } void down(int now) { if (l[now]) col[l[now]] = col[now];//顏色的傳遞 if (r[now]) col[r[now]] = col[now]; if (lzs[now]) { if (l[now]) downs(l[now]); if (r[now]) downs(r[now]); lzs[now] = 0; } } void down_line(int now) { if (nrt(now)) down_line(fa[now]); down(now); } void rotate(int x) { int y = fa[x]; int z = fa[y]; int b = (ls(x) ? r[x] : l[x]); if (z && nrt(y)) (ls(y) ? l[z] : r[z]) = x; if (ls(x)) r[x] = y, l[y] = b; else l[x] = y, r[y] = b; fa[x] = z; fa[y] = x; if (b) fa[b] = y; up(y); } void Splay(int x) { down_line(x); while (nrt(x)) { if (nrt(fa[x])) { if (ls(x) == ls(fa[x])) rotate(fa[x]); else rotate(x); } rotate(x); } up(x); } void access(int x) { int lst = 0; for (; x; x = fa[x]) { Splay(x); r[x] = 0; up(x); add_(col[x], -sz[x]);//把原來的顏色清掉 add_(tot, sz[x]);//染上新的最大顏色 r[x] = lst; up(x); lst = x; } } void make_root(int x) { tot++;//新開最大的顏色 access(x); Splay(x); col[x] = tot;//只用標記最上面的,後面的當懶標記下傳 downs(x); } int query(int x) { Splay(x); return query_(col[x] - 1) + sz[r[x]] + 1; } void dfs(int now) { col[now] = now; for (int i = le[now]; i; i = e[i].nxt) if (!col[e[i].to]) { fa[e[i].to] = now; dfs(e[i].to); if (col[e[i].to] > col[now]) {//要刪了它才能刪兒子 col[now] = col[e[i].to]; r[now] = e[i].to; } } add_(col[now], 1); up(now); } int main() { scanf("%d %d", &n, &q); for (int i = 1; i < n; i++) { scanf("%d %d", &x, &y); add(x, y); } tot = n; dfs(n); for (int j = 1; j <= q; j++) { op = getchar(); while (op != 'u' && op != 'w' && op != 'c') op = getchar(); if (op == 'u') { for (int i = 1; i <= 1; i++) getchar(); scanf("%d", &x); make_root(x); continue; } if (op == 'w') { for (int i = 1; i <= 3; i++) getchar(); scanf("%d", &x); printf("%d\n", query(x)); continue; } if (op == 'c') { for (int i = 1; i <= 6; i++) getchar(); scanf("%d %d", &x, &y); printf("%d\n", (query(x) < query(y)) ? x : y); continue; } } return 0; }