CF757G Can Bash Save the Day?(可持久化边分治)
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;
}