之前写过一个基本的树链剖分,但是我今天要讲的进阶版树链剖分要能支持以下操作:
其实还是板子题
其他的前置知识:树状数组支持区间修改区间查询(单纯图个方便),不会的话可以看这里 。
说说进阶其实也没什么东西。。。
相较于我原来写的 blog 只多了修改询问子树和换根的操作,但不得不说换根操作是真的妙,值得讲一讲。
先将换根吧。假设原来的根是 r t rt rt ,新的根是 n e w r t newrt newrt 。那么换根代码只有一行 r t = n e w r t rt = newrt rt=newrt 。
这下有人要喷了,这不什么都没干嘛。说得对,确实什么都没干,但是也很好理解,树链剖分没有 LCT 那么灵活,每次换根重构整棵树的话时间上实在支撑不起。
但是由于这个原因,修改询问子树就比原来要复杂一些了,修改和询问大致都可以分为三种情况,这里以修改为例:
x = r o o t x = root x=root
从意义上理解,整棵树的权值都要修改,直接调用树状数组的 u p d a t e update update 就可以了
在原树中( r o o t = 1 root = 1 root=1时) x x x 是 r o o t root root 的祖先,判断条件是 x x x 在 d f s dfs dfs 序中有 t i d x ≤ t i d r o o t tid_x \leq tid_{root} tidx≤tidroot 和 t e d x ≥ t e d r o o t ted_x \geq ted_{root} tedx≥tedroot 同时成立,其中 t i d x tid_x tidx 表示 x x x 在 d f s dfs dfs 序中第一次出现的位置, t e d x ted_x tedx 表示 x x x 在 d f s dfs dfs 序中最后一次出现的位置。
这种情况比较复杂,具体情况还是要自己画图,我在这里口头阐述一下。
其实画几个图就能发现这么一个东西,这种情况下修改子树权值的范围是整棵树除去一棵子树。至于那个子树的根节点嘛,,还是要画图(因为我也是画了一大堆图才明白的,要是在电脑上画这个时间开销实在太大了,但是你想背板子其实关系不大,因为代码特别清晰)。
假如你真的去画图了,你会发现这么一个规律,这棵被 lou 掉的子树的根 t t t 要么是 x x x 的重儿子,要么是 x x x 旁边的一个轻儿子(是 r o o t root root 在原树中的祖先),而到底是谁取决于 r o o t root root 蹦到 x x x 所在的重链上是否和 x x x 重合。如果不是,即为前者,反之为后者。
其他情况,易得其实换不换根没有影响,直接当原来的版本做就可以了。
class BinaryIndexedTree {
private:
long long c[2][N];
inline int lowbit(int x) { return x & (-x); }
inline void update(int x, long long v) {
for (int i = x; i <= n; i += lowbit(i)) c[0][i] += v, c[1][i] += x * v;
}
inline long long query(int x) {
long long ans = 0;
for (int i = x; i > 0; i -= lowbit(i)) ans += (x + 1) * c[0][i] - c[1][i];
return ans;
}
public:
inline void update(int l, int r, long long v) { update(l, v); update(r + 1, -v); }
inline long long query(int l, int r) { return query(r) - query(l - 1); }
} BIT;
class TreeChain {
private:
int root, parent[N], size[N], depth[N], son[N], top[N], tid[N], ted[N];
inline void dfsPre(int u, int p, int d) {
parent[u] = p, depth[u] = d, size[u] = 1;
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (v == p) continue;
dfsPre(v, u, d + 1);
size[u] += size[v];
if (size[v] > size[son[u]]) son[u] = v;
}
}
inline void dfsTop(int u, int tp) {
static int dfsClock = 0;
top[u] = tp; tid[u] = ++dfsClock;
if (son[u]) dfsTop(son[u], tp);
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (v == parent[u] || v == son[u]) continue;
dfsTop(v, v);
}
ted[u] = dfsClock;
}
inline int reach(int x, int y) {
int ans = 0;
while (top[x] != top[y]) {
ans = top[x];
x = parent[top[x]];
}
return x == y ? ans : son[y];
}
public:
inline void init(int rt) { root = rt; dfsPre(root, 0, 1); dfsTop(root, root); }
inline void changeRoot(int rt) { root = rt; }
inline void updateInit(int x, int v) { BIT.update(tid[x], tid[x], v); }
inline void updateRoute(int x, int y, int z) {
while (top[x] != top[y]) {
if (depth[top[x]] < depth[top[y]])
std::swap(x, y);
BIT.update(tid[top[x]], tid[x], z);
x = parent[top[x]];
}
if (depth[x] > depth[y])
std::swap(x, y);
BIT.update(tid[x], tid[y], z);
}
inline long long queryRoute(int x, int y) {
long long ans = 0;
while (top[x] != top[y]) {
if (depth[top[x]] < depth[top[y]])
std::swap(x, y);
ans += BIT.query(tid[top[x]], tid[x]);
x = parent[top[x]];
}
if (depth[x] > depth[y])
std::swap(x, y);
return ans + BIT.query(tid[x], tid[y]);
}
inline void updateSubtree(int x, int z) {
if (x == root)
BIT.update(1, n, z);
else if (tid[x] <= tid[root] && ted[x] >= ted[root]) {
int t = reach(root, x);
BIT.update(1, tid[t] - 1, z);
BIT.update(ted[t] + 1, n, z);
} else
BIT.update(tid[x], ted[x], z);
}
inline long long querySubtree(int x) {
if (x == root)
return BIT.query(1, n);
else if (tid[x] <= tid[root] && ted[x] >= ted[root]) {
int t = reach(root, x);
return BIT.query(1, tid[t] - 1) + BIT.query(ted[t] + 1, n);
} else
return BIT.query(tid[x], ted[x]);
}
};
必须压压行了,否则这个码量堪称恐怖,但其实仔细观察其中的一半都是可以 Ctrl + C 和 Ctrl + V 解决的。