「LNOI2014」LCA

知识点: 树链剖分

原题面:Loj Luogu

简述

给定一 \(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。

分析

先把答案改成前缀和的形式:

\[ans = \sum_{i=0}^{r} {\operatorname{dep}({\operatorname{lca}(i, z)}}) - \sum_{i=0}^{l-1} {\operatorname{dep}({\operatorname{lca}(i, z)}}) \]

先考虑如何求得单个的 \(\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; 
}
posted @ 2020-09-06 21:19  Luckyblock  阅读(199)  评论(1编辑  收藏  举报