「HNOI2015」开店

知识点: 树链剖分,可持久化线段树

原题面:Loj Luogu

这里是比较喜欢可爱的东西 的 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{ans} = \sum_{v(\operatorname{val}_v\le r)} \operatorname{dis} (u,v) - \sum_{v(\operatorname{val}_v\le l-1)}\operatorname{dis}(u,v) \]

先考虑如何求得单个的 \(\operatorname{dis}(u,v)\)。方便起见,将无根树转为有根树,设 \(1\) 为根。设 \(\operatorname{dis}(u,v)\) 表示树上节点 \(u,v\) 的最短路长度,显然有:

\[\operatorname{dis}(u,v) = \operatorname{dis}(1,u) + \operatorname{dis}(1,v) - 2\times \operatorname{dis}(1, \operatorname{lca}(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_{v(val_v\le x)}\operatorname{dis}(1,u) + \sum_{v(val_v\le x)}\operatorname{dis}(1,v) - 2\times \sum_{v(val_v\le x)}\operatorname{dis}(1, \operatorname{lca}(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; 
}
posted @ 2020-09-06 22:05  Luckyblock  阅读(176)  评论(2编辑  收藏  举报