Loj 139. 树链剖分

知识点:树链剖分

原题面 Loj

简述

给定一棵 n 个节点的树,点有点权,初始时该树的根为 1 号节点。给定 m 次操作:

  1. 换根:将一个指定的节点设置为树的新根。
  2. 修改路径权值:给定两个节点,将这两个节点间路径上的所有节点权值(含这两个节点)增加一个给定的值。
  3. 修改子树权值:给定一个节点,将以该节点为根的子树内的所有节点权值增加一个给定的值。
  4. 询问路径:询问某条路径上节点的权值和。
  5. 询问子树:询问某个子树内节点的权值和。

1n,m1050 点权、修改量105
1S,256MB。

分析

除去换根操作后就是板题了,钦定 1 为根轻重链剖分后用线段树维护 dfs 序即可,这里主要记录如何处理换根操作。

对于路径查询/修改操作,并不会被根的位置影响,套用之前的做法即可。

对于子树查询/修改操作,考虑对当前根 r 与指定节点 u 的位置关系分类讨论:

  • r=u,直接对整棵树做操作。
  • r 在原树上不在 u 的子树内,换根后对 u 子树节点的 dfs 序没有影响,做法同根为 1 的情况,直接访问对应位置即可,即有下图所示:

r 在原树上不在 u 的子树内

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

r 在原树上在 u 的子树内

代码

复制复制
//知识点:重链剖分,线段树
/*
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;
}
posted @   Luckyblock  阅读(115)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 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】
点击右上角即可分享
微信分享提示