「HNOI2015」开店
知识点: 树链剖分,可持久化线段树
这里是比较喜欢可爱的东西 的 18 岁少女幽香和八云紫的姬情互动照pianklnaiodioaniojwadawiojdmwadmiusfmwl;amd;lakdp
简述
给定一棵 \(n\) 个结点的树,点有点权 \(\operatorname{val}\),边有边权 \(\operatorname{w}\)。
给定 \(q\) 次询问,每次询问给定参数 \(u, l, r\),求 \(u\) 与点权在 \([l,r]\) 内的节点的最短路长度之和。
强制在线。
\(1\le n\le 1.5\times 10^5\),\(1\le q\le 2\times 10^5\)。
分析
先将各点按照点权值进行排序,点权值在 \([l,r]\) 的点位于一段连续的子区间内。可考虑把答案拆成前缀和的形式:
先考虑如何求得单个的 \(\operatorname{dis}(u,v)\)。方便起见,将无根树转为有根树,设 \(1\) 为根。设 \(\operatorname{dis}(u,v)\) 表示树上节点 \(u,v\) 的最短路长度,显然有:
直接 dfs 预处理出 \(1\) 到各点的最短路,可直接得到 \(\operatorname{dis}(1,u)\) 和 \(\operatorname{dis}(1,v)\),考虑如何快速求 \(\operatorname{dis}(1, \operatorname{lca}(u,v))\)。
发现查询对象变成了类似深度的形式。把树画出来,发现 \(\operatorname{dis}(1, {\operatorname{lca}(u,v)})\) 为路径 \(0\rightarrow i\) 与 路径 \(0\rightarrow z\) 的相交部分的长度。问题变为快速求两段 起点相同 的路径 相交 部分的长度。
考虑在路径上打标记,令路径 \(1\rightarrow u\) 上所有节点权值加上 连接父节点的边 的权值,显然答案为 \(1\rightarrow z\) 路径上的点权之和。边权值固定,对于一个节点,每次修改对其点权值的贡献均相等。
考虑树链剖分,维护每个节点被修改的次数,其对询问的贡献为 修改次数 \(\times\) 连接父节点的边 的权值。
发现这样可以扩展到多个询问的情况。对于询问 \(\sum\limits_{v(val_v\le x)} \operatorname{dis}(u,v)\),可拆成:
\(\sum\operatorname{dis}(1,u)\) 可直接 \(O(1)\) 求得。\(\sum\operatorname{dis}(1,v)\) 可维护一下标为 排序后节点编号 的前缀和,二分到最大的 \(v\),其缀和即为答案。
对于 \(\sum \operatorname{dis}(\operatorname{lca}(u,v))\),按照上述方法,先修改所有路径 \(1\rightarrow v\),查询 \(1\rightarrow u\) 的点权值和即可。
再考虑最开始的拆分,将两个端点的询问拆成了一个端点的询问。显然 \(\sum\limits_{v(val_v\le x)} \operatorname{dis}(u,v)\) 需要在插入到最大的 \(v\) 后接着进行查询,若每个询问都重新插入一次,时间开销承受不住。若不强制在线,可考虑对询问排序,做一个扫描线,在枚举 \(v\) 时顺便进行询问。强制在线,可考虑每次插入后都对线段树进行可持久化。
时间复杂度为 \(O(q\log^2 n)\),空间复杂度为 \(O(n\log^2 n)\)。
注意一些实现的细节,在代码中用注释进行了标记。
爆零小技巧
如果有多个含义相近名字相近的变量时,要多加小心。
建议通读代码进行检查。
区间修改的可持久化线段树空间也是 \(O(\log m)\) 级别的。
因为一个区间最多被分为 \(\log m\) 个线段树区间,遍历的点的数量 \(\le 2\log m\) 个。
不过下放时会新建巨多个新节点,必须写标记永久化。
代码
//知识点:树链剖分,可持久化线段树
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define ll long long
#define pair std::pair
#define mp std::make_pair
const int kMaxn = 2e5 + 10;
const int kInf = 1e9 + 2077;
//=============================================================
pair <int, int> node[kMaxn];
int root[kMaxn << 1];
int n, m, mod, edge_num, head[kMaxn], v[kMaxn << 1], w[kMaxn << 1], ne[kMaxn << 1];
ll ans, dis[kMaxn], sumw[kMaxn], sumdis[kMaxn];
//=============================================================
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 GetMax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
void GetMin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
void AddEdge(int u_, int v_, int w_) {
v[++ edge_num] = v_, w[edge_num] = w_;
ne[edge_num] = head[u_], head[u_] = edge_num;
}
//The Scientfic Concept of Development Tree
namespace SegmentTree {
#define ls (lson[now_])
#define rs (rson[now_])
#define mid ((L_+R_)>>1)
int node_num, lson[kMaxn << 6], rson[kMaxn << 6], cnt[kMaxn << 6];
ll sum[kMaxn << 6];
void Modify(int &now_, int pre_, int L_, int R_, int l_, int r_) {
now_ = ++ node_num;
lson[now_] = lson[pre_], rson[now_] = rson[pre_];
sum[now_] = sum[pre_], cnt[now_] = cnt[pre_];
if (l_ == L_ && R_ == r_) {
cnt[now_] ++;
return ;
}
//错写成 sum
sum[now_] += (sumw[r_] - sumw[l_ - 1]); //加上对应dfn序内维护的边权
if (r_ <= mid) Modify(ls, lson[pre_], L_, mid, l_, r_);
else if (l_ > mid) Modify(rs, rson[pre_], mid + 1, R_, l_, r_); //误写成 R_
else {
Modify(ls, lson[pre_], L_, mid, l_ ,mid);
Modify(rs, rson[pre_], mid + 1, R_, mid + 1, r_); ////注意区间的分裂
}
}
ll Query(int now_, int L_, int R_, int l_, int r_) {
//标记永久化,注意区间的分裂。
ll ret = 1ll * (sumw[r_] - sumw[l_ - 1]) * cnt[now_];
if (l_ == L_ && R_ == r_) return ret + sum[now_];
if (r_ <= mid) return ret + Query(ls, L_, mid, l_, r_);
if (l_ > mid) return ret + Query(rs, mid + 1, R_, l_, r_); //误写成 R_
return ret + Query(ls, L_, mid, l_ ,mid) + Query(rs, mid + 1, R_, mid + 1, r_);
}
}
namespace TreeChainCut {
int fa[kMaxn], size[kMaxn], son[kMaxn], top[kMaxn];
int dfn_num, dfn[kMaxn];
int upper_edge[kMaxn];
//链剖操作的链一端均为根,不需要处理 dep。
void Dfs1(int u_, int fa_) {
size[u_] = 1;
fa[u_] = fa_;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i], w_ = w[i];
if (v_ == fa_) continue;
dis[v_] = dis[u_] + w_;
upper_edge[v_] = w_;
Dfs1(v_, u_);
size[u_] += size[v_];
if (size[v_] > size[son[u_]]) son[u_] = v_;
}
}
void Dfs2(int u_, int top_) {
top[u_] = top_;
dfn[u_] = ++ dfn_num;
sumw[dfn_num] = upper_edge[u_];
if (son[u_]) Dfs2(son[u_], top_);
for (int i = head[u_]; i; i = ne[i]) {
if (v[i] == son[u_] || v[i] == fa[u_]) continue;
Dfs2(v[i], v[i]);
}
}
void Modify(int u_, int now_) {
//把多条重链上的修改,看成多次区间不相交的区间修改。
//则每次修改,本质上都创建了一个新版本。
//但只需要记录最后的版本,注意 root 的赋值。
root[now_] = root[now_ - 1];
for (; top[u_] != 1; u_ = fa[top[u_]]) {
SegmentTree :: Modify(root[now_], root[now_], 1, n, dfn[top[u_]], dfn[u_]);
}
SegmentTree :: Modify(root[now_], root[now_], 1, n, 1, dfn[u_]);
}
ll Query(int root_, int u_) {
ll ret = 0;
for (; top[u_] != 1; u_ = fa[top[u_]]) {
ret += SegmentTree :: Query(root_, 1, n, dfn[top[u_]], dfn[u_]);
}
ret += SegmentTree :: Query(root_, 1, n, 1, dfn[u_]);
return ret;
}
}
//=============================================================
int main() {
n = read(), m = read(), mod = read();
for (int i = 1; i <= n; ++ i) {
node[i] = mp <int, int> (read(), (int) i);
}
for (int i = 1; i < n; ++ i) {
int u_ = read(), v_ = read(), w_ = read();
AddEdge(u_, v_, w_), AddEdge(v_, u_, w_);
}
std :: sort(node + 1, node + n + 1);
TreeChainCut :: Dfs1(1, 0);
TreeChainCut :: Dfs2(1, 1);
//下标均为 dfn 序。
for (int i = 1; i <= n; ++ i) {
sumw[i] += sumw[i - 1];
sumdis[i] += sumdis[i - 1] + dis[node[i].second];
}
for (int i = 1; i <= n; ++ i) {
TreeChainCut :: Modify(node[i].second, i);
}
for (int i = 1; i <= m; ++ i) {
int u_ = read();
int a = (1ll * read() + ans) % mod;
int b = (1ll * read() + ans) % mod;
if (a > b) std :: swap(a, b);
a = std :: lower_bound(node + 1, node + n + 1, mp <int, int> ((int) a, 0)) - node;
b = std :: upper_bound(node + 1, node + n + 1, mp <int, int> ((int) b, (int) kInf)) - node - 1;
ans = 1ll * (b - a + 1) * dis[u_] + sumdis[b] - sumdis[a - 1];
ans -= 2ll * (TreeChainCut :: Query(root[b], u_) - TreeChainCut :: Query(root[a - 1], u_));
printf("%lld\n", ans);
}
return 0;
}