Loj 139. 树链剖分
知识点:树链剖分
原题面 Loj
简述
给定一棵 个节点的树,点有点权,初始时该树的根为 号节点。给定 次操作:
- 换根:将一个指定的节点设置为树的新根。
- 修改路径权值:给定两个节点,将这两个节点间路径上的所有节点权值(含这两个节点)增加一个给定的值。
- 修改子树权值:给定一个节点,将以该节点为根的子树内的所有节点权值增加一个给定的值。
- 询问路径:询问某条路径上节点的权值和。
- 询问子树:询问某个子树内节点的权值和。
, 点权、修改量。
1S,256MB。
分析
除去换根操作后就是板题了,钦定 1 为根轻重链剖分后用线段树维护 dfs 序即可,这里主要记录如何处理换根操作。
对于路径查询/修改操作,并不会被根的位置影响,套用之前的做法即可。
对于子树查询/修改操作,考虑对当前根 与指定节点 的位置关系分类讨论:
- 若 ,直接对整棵树做操作。
- 若 在原树上不在 的子树内,换根后对 子树节点的 dfs 序没有影响,做法同根为 1 的情况,直接访问对应位置即可,即有下图所示:

在原树上不在 的子树内
- 若 在原树上在 的子树内,如下图所示。考虑补集转化,先操作整棵树,再减去以节点 为根子树的贡献。可以发现节点 即链 上距离 最近的节点,找到这类节点时可以通过跳重链实现,详见代码。

在原树上在 的子树内
代码
复制复制//知识点:重链剖分,线段树 /* By:Luckyblock */ #include <algorithm> #include <cctype> #include <cstdio> #include <cstring> #define LL long long const int kN = 1e5 + 10; //============================================================= int n, m, root, ori[kN], a[kN]; int e_num, head[kN], v[kN << 1], ne[kN << 1]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); return f * w; } void Chkmax(int &fir_, int sec_) { if (sec_ > fir_) fir_ = sec_; } void Chkmin(int &fir_, int sec_) { if (sec_ < fir_) fir_ = sec_; } void AddEdge(int u_, int v_) { v[++ e_num] = v_; ne[e_num] = head[u_]; head[u_] = e_num; } namespace Seg { #define ls (now_<<1) #define rs (now_<<1|1) #define mid ((L_+R_)>>1) LL sum[kN << 2], tag[kN << 2]; void Pushup(int now_) { sum[now_] = (sum[ls] + sum[rs]); } void Pushdown(int now_, int L_, int R_) { sum[ls] += 1ll * tag[now_] * (mid - L_ + 1); sum[rs] += 1ll * tag[now_] * (R_ - mid); tag[ls] += tag[now_]; tag[rs] += tag[now_]; tag[now_] = 0ll; } void Build(int now_, int L_, int R_) { if (L_ == R_) { sum[now_] = a[L_]; tag[now_] = 0ll; return ; } Build(ls, L_, mid), Build(rs, mid + 1, R_); Pushup(now_); } void Modify(int now_, int L_, int R_, int l_, int r_, LL val_) { if (l_ <= L_ && R_ <= r_) { sum[now_] += 1ll * (R_ - L_ + 1) * val_; tag[now_] += val_; return ; } Pushdown(now_, L_, R_); if (l_ <= mid) Modify(ls, L_, mid, l_, r_, val_); if (r_ > mid) Modify(rs, mid + 1, R_, l_, r_, val_); Pushup(now_); } LL Query(int now_, int L_, int R_, int l_, int r_) { if (l_ <= L_ && R_ <= r_) return sum[now_]; Pushdown(now_, L_, R_); LL ret = 0; if (l_ <= mid) ret += Query(ls, L_, mid, l_, r_); if (r_ > mid) ret += Query(rs, mid + 1, R_, l_, r_); return ret ; } #undef ls #undef rs #undef mid } namespace Cut { int fa[kN], son[kN], dep[kN], sz[kN], top[kN]; int d_num, dfn[kN]; void Dfs1(int u_, int fa_) { fa[u_] = fa_; sz[u_] = 1; dep[u_] = dep[fa_] + 1; for (int i = head[u_]; i; i = ne[i]) { int v_ = v[i]; if (v_ == fa_) continue ; Dfs1(v_, u_); if (sz[v_] > sz[son[u_]]) son[u_] = v_; sz[u_] += sz[v_]; } } void Dfs2(int u_, int top_) { dfn[u_] = ++ d_num; a[d_num] = ori[u_]; top[u_] = top_; if (son[u_]) Dfs2(son[u_], top_); for (int i = head[u_]; i; i = ne[i]) { int v_ = v[i]; if (v_ == fa[u_] || v_ == son[u_]) continue ; Dfs2(v_, v_); } } void Modify(int u_, int v_, int val_) { for (; top[u_] != top[v_]; u_ = fa[top[u_]]) { if (dep[top[u_]] < dep[top[v_]]) std::swap(u_, v_); Seg::Modify(1, 1, n, dfn[top[u_]], dfn[u_], val_); } if (dep[u_] < dep[v_]) std::swap(u_, v_); Seg::Modify(1, 1, n, dfn[v_], dfn[u_], val_); } LL Query(int u_, int v_) { LL ret = 0; for (; top[u_] != top[v_]; u_ = fa[top[u_]]) { if (dep[top[u_]] < dep[top[v_]]) std::swap(u_, v_); ret += Seg::Query(1, 1, n, dfn[top[u_]], dfn[u_]); } if (dep[u_] < dep[v_]) std::swap(u_, v_); ret += Seg::Query(1, 1, n, dfn[v_], dfn[u_]); return ret; } int Child(int u_, int v_) { //求链 v_ -> u_ 上距离 v_ 最近的节点 while (top[u_] != top[v_]) { u_ = top[u_]; if (fa[u_] == v_) return u_; //u_ 是某条重链顶,v_ 是某重链底 u_ = fa[u_]; } return son[v_]; //u_, v_ 在同一重链 } } #define dfn Cut::dfn #define sz Cut::sz void ModifySubtree(int u_, int val_) { if (u_ == root) { Seg::Modify(1, 1, n, 1, n, val_); } else if (dfn[u_] <= dfn[root] && //root 在 u_ 子树内,通过 dfs 序判断 dfn[root] + sz[root] - 1 <= dfn[u_] + sz[u_] - 1) { int pos_ = Cut::Child(root, u_); Seg::Modify(1, 1, n, 1, n, val_); //补集转化 Seg::Modify(1, 1, n, dfn[pos_], dfn[pos_] + sz[pos_] - 1, -val_); } else { Seg::Modify(1, 1, n, dfn[u_], dfn[u_] + sz[u_] - 1, val_); } } LL QuerySubtree(int u_) { if (u_ == root) return Seg::Query(1, 1, n, 1, n); if (dfn[u_] <= dfn[root] && //root 在 u_ 子树内 dfn[root] + sz[root] - 1 <= dfn[u_] + sz[u_] - 1) { int pos_ = Cut::Child(root, u_); return Seg::Query(1, 1, n, 1, n) - Seg::Query(1, 1, n, dfn[pos_], dfn[pos_] + sz[pos_] - 1); } return Seg::Query(1, 1, n, dfn[u_], dfn[u_] + sz[u_] - 1); } //============================================================= int main() { n = read(), root = 1; for (int i = 1; i <= n; ++ i) ori[i] = read(); for (int i = 2; i <= n; ++ i) { int u_ = read(), v_ = i; AddEdge(u_, v_); } Cut::Dfs1(root, 0), Cut::Dfs2(root, root); Seg::Build(1, 1, n); m = read(); while (m --) { int opt = read(); if (opt == 1) { root = read(); } else if (opt == 2) { int u_ = read(), v_ = read(), val_ = read(); Cut::Modify(u_, v_, val_); } else if (opt == 3) { int x_ = read(), val_ = read(); ModifySubtree(x_, val_); } else if (opt == 4) { int u_ = read(), v_ = read(); printf("%lld\n", Cut::Query(u_, v_)); } else if (opt == 5) { int x_ = read(); printf("%lld\n", QuerySubtree(x_)); } } return 0; }
作者@Luckyblock,转载请声明出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】