「LNOI2014」LCA
知识点: 树链剖分
简述
给定一 \(n\) 个节点的有根树,编号为 \(0\sim n - 1\),根节点为 \(0\)。节点 \(i\) 的 深度定义为节点 \(i\) 到根的距离 \(+1\),记为 \(\operatorname{dep}(i)\)。设 \(\operatorname{lca}(i,j)\) 表示 \(i,j\) 的最近公共祖先。
给定 \(q\) 次询问,每次询问给定参数 \(l,r,z\),求:\[\sum_{i=l}^{r} {\operatorname{dep}({\operatorname{lca}(i, z)}}) \]\(1\le n,q\le 5\times 10^4\)。
1S,128MB。
分析
先把答案改成前缀和的形式:
先考虑如何求得单个的 \(\operatorname{dep}({\operatorname{lca}(i,z)})\)。把树画出来,发现 \(\operatorname{dep}({\operatorname{lca}(i,z)})\) 为路径 \(0\rightarrow i\) 与 路径 \(0\rightarrow z\) 的相交部分的长度。
问题变为快速求两段 起点相同 的路径 相交 部分的长度。考虑在路径上打标记,令路径 \(0\rightarrow i\) 上所有节点权值 \(+1\),显然答案即为 \(0\rightarrow z\) 路径上的点权之和。
扩展到 \(\sum \operatorname{dep}({\operatorname{lca}(i,z)})\),发现上面的方法依然适用。对于一个询问 \(\sum\limits_{i=0}^{x}\operatorname{dep}(\operatorname{lca}(i,z))\),先将所有 \(0\rightarrow i\) 上的点权 \(+1\),再查询 \(0\rightarrow z\) 上的点权和即可。
考虑离线询问,将一个区间询问 拆成 两个单点询问,将询问排序后做一个扫描线。顺序枚举路径 \(0\rightarrow i\) 进行修改,当 \(i\) 等于某个询问的要求时则进行询问。
树链剖分维护即可,复杂度 \(O(q\log^2 n)\)。
注意不同题目中变量的实际含义。
代码
//知识点:树链剖分
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define ll long long
const int kMaxn = 5e4 + 10;
const int kMod = 201314;
//=============================================================
struct Query {
int id, pos, z, val;
} que[kMaxn << 1];
int n, q, edge_num, head[kMaxn], v[kMaxn], ne[kMaxn];
int ans[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_) {
v[++ edge_num] = v_, ne[edge_num] = head[u_], head[u_] = edge_num;
}
bool CompareQuery(Query fir, Query sec) {
return fir.pos < sec.pos;
}
namespace SegmentTree {
#define ls (now_<<1)
#define rs (now_<<1|1)
#define mid ((L_+R_)>>1)
int sum[kMaxn << 2], tag[kMaxn << 2];
void Pushup(int now_) {
sum[now_] = sum[ls] + sum[rs];
}
void Pushdown(int now_, int L_, int R_) {
if (! tag[now_]) return ;
sum[ls] = (sum[ls] + tag[now_] * (mid - L_ + 1)) % kMod;
sum[rs] = (sum[rs] + tag[now_] * (R_ - (mid + 1) + 1)) % kMod;
tag[ls] = (tag[ls] + tag[now_]) % kMod;
tag[rs] = (tag[rs] + tag[now_]) % kMod;
tag[now_] = 0;
}
void Modify(int now_, int L_, int R_, int l_, int r_, int val_) {
if (l_ <= L_ && R_ <= r_) {
sum[now_] = (sum[now_] + (R_ - L_ + 1) * val_) % kMod;
tag[now_] = (tag[now_] + val_) % kMod;
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_);
}
int Query(int now_, int L_, int R_, int l_, int r_) {
if (l_ <= L_ && R_ <= r_) return sum[now_];
Pushdown(now_, L_, R_);
int ret = 0;
if (l_ <= mid) ret = (ret + Query(ls, L_, mid, l_, r_)) % kMod;
if (r_ > mid) ret = (ret + Query(rs, mid + 1, R_, l_, r_)) % kMod;
return ret;
}
}
namespace TreeChainCut {
int fa[kMaxn], dep[kMaxn], size[kMaxn], son[kMaxn], top[kMaxn];
int dfn_num, dfn[kMaxn];
void Dfs1(int u_, int fa_) {
size[u_] = 1;
dep[u_] = dep[fa_] + 1;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i];
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;
if (son[u_]) Dfs2(son[u_], top_);
for (int i = head[u_]; i; i = ne[i]) {
if (v[i] == son[u_]) continue;
Dfs2(v[i], v[i]);
}
}
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_); //注意 top
SegmentTree :: Modify(1, 1, n, dfn[top[u_]], dfn[u_], val_);
}
if (dep[u_] > dep[v_]) std :: swap(u_, v_);
SegmentTree :: Modify(1, 1, n, dfn[u_], dfn[v_], val_);
}
int Query(int u_, int v_) {
int ret = 0;
for (; top[u_] != top[v_]; u_ = fa[top[u_]]) {
if (dep[top[u_]] < dep[top[v_]]) std :: swap(u_, v_); //
ret += SegmentTree :: Query(1, 1, n, dfn[top[u_]], dfn[u_]);
ret %= kMod;
}
if (dep[u_] > dep[v_]) std :: swap(u_, v_);
ret += SegmentTree :: Query(1, 1, n, dfn[u_], dfn[v_]);
return ret % kMod;
}
}
//=============================================================
int main() {
n = read(), q = read();
for (int i = 2; i <= n; ++ i) {
TreeChainCut :: fa[i] = read() + 1;
AddEdge(TreeChainCut :: fa[i], i);
}
TreeChainCut :: Dfs1(1, 0);
TreeChainCut :: Dfs2(1, 1);
for (int i = 1; i <= q; ++ i) {
int l = read(), r = read() + 1, z = read() + 1;
que[i] = (Query) {i, l, z, - 1};
que[i + q] = (Query) {i, r, z, 1};
}
std :: sort(que + 1, que + 2 * q + 1, CompareQuery);
int nowq = 1;
for (int i = 1; i <= n; ++ i) {
if (nowq > 2 * q) break; //写成 n
TreeChainCut :: Modify(1, i, 1);
for (; que[nowq].pos <= i && nowq <= 2 * q; ++ nowq) { //写成 n
if (! que[nowq].pos) continue;
int id = que[nowq].id, z = que[nowq].z, val = que[nowq].val;
int ret = val * TreeChainCut :: Query(1, z) % kMod;
ans[id] = ((ans[id] + ret) % kMod + kMod) % kMod;
}
}
for (int i = 1; i <= q; ++ i) printf("%d\n", ans[i]);
return 0;
}