CF757G Can Bash Save the Day?(可持久化邊分治)
阿新 • • 發佈:2020-07-23
CF757G Can Bash Save the Day?(可持久化邊分治)
題目大意
一棵 \(n\) 個點的樹和一個排列 \(p_i\) ,邊有邊權,支援兩種操作:
- \(l\ r\ x\),詢問 \(\sum\limits _{i=l}^{r} dis(p_i,x)\)
- \(x\),交換 \(p_x,p_{x+1}\)
資料範圍
\(n,q\leq 2\times 10^5\),強制線上
解題思路
牛逼題
顯然這是一道老經典題了,考慮開店那題,直接動態點分治上整個字首和,二分查詢即可
考慮有修改,每個點整個平衡樹或者動態開點線段樹即可,都是 \(\Theta(n\log^2n)\) 的,其中第一個做法常數大,第二個做法空間大,都過不去
還有另外一種思路,考慮所有距離和等於 \(\sum (dep_x+dep_y)-2*\sum dep_{lca(x,y)}\)
前面那個很好求,後面那個考慮樹鏈剖分,可以想象為先把 y 到根的鏈打上標記,然後從 x 跳到根路徑上的標記長度就是 \(dep_{lca(x,y)}\),具體來說打標記的方式可以維護每一條邊被覆蓋的次數,然後乘上整體的區間長度即可,把線段樹換成主席樹標記永久化即可實現靜態,考慮如何交換相鄰兩項,由於是和的形式,容易發現後面一項一直到最後一項的主席樹沒有變化,只需要把第 x 個主席樹重構一下即可,但顯然這個做法雖然常數小了一點,但時間複雜度和空間複雜度還是爆炸的
考慮邊分治這種神仙做法,邊分治可以可持久化!當然點分治也可以但是比邊分治要麻煩一些吧
可以看看暴力寫掛那題,邊分樹是可以合併的,那麼也意味著它可以支援可持久化,就像線段樹一樣
每個點當葉子都可以取出一條從根(邊分治的根)到它的鏈,我們取出待用,取原題排列一個,按順序將所有鏈輕輕合併,查詢時用第 r 棵邊分樹減去第 l - 1 邊分樹的答案即可,交換相鄰兩項時直接重構前一棵主席邊分樹即可
我們邊分樹真的是太厲害了
經檢驗,程式碼可讀
const int N = 500500; struct Tree { int ls, rs; ll vl, vr, sl, sr; #define sl(p) tree[p].sl #define sr(p) tree[p].sr #define ls(p) tree[p].ls #define rs(p) tree[p].rs #define vl(p) tree[p].vl #define vr(p) tree[p].vr }tree[N * 32]; int rt[N], nodecnt, cnt, n, q; namespace Conquer { const int N = 1500050; int h[N], ne[N<<1], to[N<<1], w[N<<1], tot = 1; inline void add(int x, int y, int z) { ne[++tot] = h[x], to[h[x] = tot] = y, w[tot] = z; } int Siz, siz[N], vis[N], las[N], lim, ed; inline void adde(int x, int y, int z) { add(x, y, z), add(y, x, z); // write(x, ' '), write(y, ' '), write(z); } void get(int x, int fa) { siz[x] = 1; for (int i = h[x], y; i; i = ne[i]) { if ((y = to[i]) == fa || vis[i]) continue; get(y, x), siz[x] += siz[y]; int tp = max(siz[y], Siz - siz[y]); if (tp < lim) lim = tp, ed = i; } } void dfs(int x, int fa, ll Dis, int ty) { if (x <= n) { ++nodecnt, !rt[x] && (rt[x] = nodecnt); if (ls(las[x]) == -1) ls(las[x]) = nodecnt; else rs(las[x]) = nodecnt; if (!ty) ls(nodecnt) = -1, vl(nodecnt) = Dis, sl(nodecnt)++; else rs(nodecnt) = -1, vr(nodecnt) = Dis, sr(nodecnt)++; las[x] = nodecnt; } for (int i = h[x], y; i; i = ne[i]) if ((y = to[i]) != fa && !vis[i]) dfs(y, x, Dis + w[i], ty); } void conquer(int x, int S) { if (S <= 1) return; Siz = lim = S, get(x, 0), vis[ed] = vis[ed ^ 1] = 1; int tx = to[ed], ty = to[ed ^ 1]; // write(tx, ' '), write(ty); dfs(tx, 0, 0, 0), dfs(ty, 0, w[ed], 1); conquer(ty, S - siz[tx]), conquer(tx, siz[tx]); } } int h[N], ne[N<<1], to[N<<1], w[N<<1], las[N], tot; inline void add(int x, int y, int z) { ne[++tot] = h[x], to[h[x] = tot] = y, w[tot] = z; } void dfs(int x, int fa) { for (int i = h[x], y; i; i = ne[i]) { if ((y = to[i]) == fa) continue; dfs(y, x); if (!las[x]) Conquer::adde(las[x] = x, y, w[i]); else { Conquer::adde(las[x], ++cnt, 0); Conquer::adde(las[x] = cnt, y, w[i]); } } } int update(int pre, int nw) { int rt = ++nodecnt; tree[rt] = tree[pre]; vl(rt) += vl(nw), vr(rt) += vr(nw), sl(rt) += sl(nw), sr(rt) += sr(nw); if (sl(nw) > 0) ls(rt) = update(ls(rt), ls(nw)); if (sr(nw) > 0) rs(rt) = update(rs(rt), rs(nw)); return rt; } ll query(int nw, int p1, int p2) { if (sl(nw) > 0) return query(ls(nw), ls(p1), ls(p2)) + vr(p2) - vr(p1) + (sr(p2) - sr(p1)) * vl(nw); if (sr(nw) > 0) return query(rs(nw), rs(p1), rs(p2)) + vl(p2) - vl(p1) + (sl(p2) - sl(p1)) * vr(nw); return 0; } const int B = (1 << 30) - 1; ll ans, T[N], p[N]; int main() { read(n), read(q), cnt = n; for (int i = 1;i <= n; i++) read(p[i]); for (int i = 1, x, y, z;i < n; i++) read(x), read(y), read(z), add(x, y, z), add(y, x, z); dfs(1, 0), Conquer::conquer(1, cnt), tree[0] = tree[nodecnt+10]; for (int i = 1;i <= nodecnt; i++) Mx(ls(i), 0), Mx(rs(i), 0); for (int i = 1;i <= n; i++) T[i] = update(T[i-1], rt[p[i]]); while (q--) { ll op, x, y, z; read(op); if (op == 1) { read(x), read(y), read(z); x = ans ^ x, y ^= ans, z ^= ans; write(ans = query(rt[z], T[x - 1], T[y])); ans &= B; } else { read(x), x ^= ans, swap(p[x], p[x+1]); T[x] = update(T[x-1], rt[p[x]]); } } return 0; }