【ACWing】2524. 树链剖分II

题目地址:

https://www.acwing.com/problem/content/2526/

给定一棵树,树中包含 n n n个节点(编号 1 ∼ n 1∼n 1n),其中第 i i i个节点的权值为 a i a_i ai。初始时, 1 1 1号节点为树的根节点。现在要对该树进行 m m m次操作,操作分为以下 5 5 5种类型:
1 u,换根,将节点 u u u设为新的树根。
2 u v k,修改路径上节点权值,将节点 u u u和节点 v v v之间路径上的所有节点(包括这两个节点)的权值增加 k k k
3 u k,修改子树上节点权值,将以节点 u u u为根的子树上的所有节点的权值增加 k k k
4 u v,询问路径,询问节点 u u u和节点 v v v之间路径上的所有节点(包括这两个节点)的权值和。
5 u,询问子树,询问以节点 u u u为根的子树上的所有节点的权值和。

输入格式:
第一行包含一个整数 n n n,表示节点个数。
第二行包含 n n n个整数,其中第 i i i个整数表示 a i a_i ai
第三行包含 n − 1 n−1 n1个整数,其中第 i i i个整数表示第 i + 1 i+1 i+1号节点的父节点编号。
第四行包含一个整数 m m m,表示操作次数。
接下来 m m m行,每行包含一个操作,格式如题目所述。

输出格式:
对于每个操作 4 4 4和操作 5 5 5,输出一行一个整数表示答案。

数据范围:
1 ≤ n , m ≤ 1 0 5 1≤n,m≤10^5 1n,m105
0 ≤ a i , k ≤ 1 0 5 0≤a_i,k≤10^5 0ai,k105
1 ≤ u , v ≤ n 1≤u,v≤n 1u,vn

先做树链剖分。操作 2 2 2和操作 4 4 4和谁是树根没关系,可以按照模板https://blog.csdn.net/qq_46105170/article/details/125497358直接操作。最主要的是如何处理换根。原树根为 1 1 1,开个全局变量 r r r,存当前的树根。接下来处理询问,假设是询问 u u u子树的总权值,分三种情况讨论:
1、如果 u = r u=r u=r,则就是询问整棵树的总权值;
2、如果 u u u r r r的最近公共祖先不是 u u u,那么修改 u u u子树与 1 1 1是树根还是 r r r是树根没关系,可以当成是对原树的询问;
3、如果 u u u r r r的最近公共祖先是 u u u,说明 r r r u u u子树里,那么要求沿着 r r r向上走到 u u u的下面一步的节点 v v v,先询问整棵树的总权值 S S S,再询问 v v v子树的权值 s s s,答案即为 S − s S-s Ss

找最近公共祖先也可以用树链剖分做,每次将重链头深的那个节点向上跳,每次跳到重链头的父节点处,直到两个节点重链头相等,此时深度浅的即为最近公共祖先。

求沿着 r r r向上走到 u u u的下面一步的节点 v v v,这一操作也可以用树链剖分。只要 r r r的重链头与 u u u的重链头不一样就进行循环,如果 r r r的重链头的父亲就是 u u u,则说明 v v v就是 r r r重链头;否则就将 r r r跳到其重链头的父亲处。退出循环的时候说明 r r r u u u已经在同一条重链上了,那么 v v v就是 u u u的重儿子。

对子树进行修改的操作与上面类似做。代码如下:

#include <iostream>
#include <cstring>
using namespace std;

const int N = 1e5 + 10;
int n, m;
int h[N], e[N], ne[N], idx, w[N];
int id[N], nw[N], cnt;
int dep[N], son[N], top[N], sz[N], fa[N];
int root;
struct Tree {
  int l, r;
  long sum, add;
} tr[N << 2];

void add(int a, int b) {
  e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

void dfs1(int u, int depth) {
  dep[u] = depth, sz[u] = 1;
  for (int i = h[u]; ~i; i = ne[i]) {
    int v = e[i];
    dfs1(v, depth + 1);
    sz[u] += sz[v];
    if (sz[son[u]] < sz[v]) son[u] = v;
  }
}

void dfs2(int u, int t) {
  id[u] = ++cnt, nw[cnt] = w[u], top[u] = t;
  if (!son[u]) return;
  dfs2(son[u], t);
  for (int i = h[u]; ~i; i = ne[i]) {
    int v = e[i];
    if (v == son[u]) continue;
    dfs2(v, v);
  }
}

void pushup(int u) {
  tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

void pushdown(int u) {
  auto &root = tr[u], &l = tr[u << 1], &r = tr[u << 1 | 1];
  if (root.add) {
    l.sum += root.add * (l.r - l.l + 1);
    l.add += root.add;
    r.sum += root.add * (r.r - r.l + 1);
    r.add += root.add;
    root.add = 0;
  }
}

void build(int u, int l, int r) {
  tr[u] = {l, r, nw[l], 0};
  if (l == r) return;
  int mid = l + r >> 1;
  build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
  pushup(u);
}

void modify(int u, int l, int r, long k) {
  if (l <= tr[u].l && tr[u].r <= r) {
    tr[u].add += k;
    tr[u].sum += k * (tr[u].r - tr[u].l + 1);
    return;
  }

  pushdown(u);
  int mid = tr[u].l + tr[u].r >> 1;
  if (l <= mid) modify(u << 1, l, r, k);
  if (r > mid) modify(u << 1 | 1, l, r, k);
  pushup(u);
}

long query(int u, int l, int r) {
  if (l <= tr[u].l && tr[u].r <= r) return tr[u].sum;
  pushdown(u);
  int mid = tr[u].l + tr[u].r >> 1;
  long res = 0;
  if (l <= mid) res += query(u << 1, l, r);
  if (r > mid) res += query(u << 1 | 1, l, r);
  return res;
}

void add_path(int u, int v, int k) {
  while (top[u] != top[v]) {
    if (dep[top[u]] < dep[top[v]]) swap(u, v);
    modify(1, id[top[u]], id[u], k);
    u = fa[top[u]];
  }
  if (dep[u] < dep[v]) swap(u, v);
  modify(1, id[v], id[u], k);
}

long query_path(int u, int v) {
  long res = 0;
  while (top[u] != top[v]) {
    if (dep[top[u]] < dep[top[v]]) swap(u, v);
    res += query(1, id[top[u]], id[u]);
    u = fa[top[u]];
  }
  if (dep[u] < dep[v]) swap(u, v);
  res += query(1, id[v], id[u]);
  return res;
}

int lca(int u, int v) {
  while (top[u] != top[v]) {
    // 谁的重链头深度深,谁就向上跳
    if (dep[top[u]] < dep[top[v]]) swap(u, v);
    u = fa[top[u]];
  }
  return dep[u] < dep[v] ? u : v;
}

// 输入保证u属于v子树
int get_son(int u, int v) {
  while (top[u] != top[v]) {
    if (fa[top[u]] == v) return top[u];
    u = fa[top[u]];
  }
  return son[v];
}

void add_sub(int u, int k) {
  modify(1, id[u], id[u] + sz[u] - 1, k);
}

void add_tree(int u, int k) {
  if (u == root) modify(1, 1, n, k);
  else if (lca(u, root) != u) modify(1, id[u], id[u] + sz[u] - 1, k);
  else {
    int x = get_son(root, u);
    modify(1, 1, n, k);
    modify(1, id[x], id[x] + sz[x] - 1, -k);
  }
}

long query_tree(int u) {
  if (u == root) return query(1, 1, n);
  if (lca(u, root) != u) return query(1, id[u], id[u] + sz[u] - 1);
  int x = get_son(root, u);
  return query(1, 1, n) - query(1, id[x], id[x] + sz[x] - 1);
}

int main() {
  memset(h, -1, sizeof h);
  scanf("%d", &n);
  for (int i = 1; i <= n; i++) scanf("%ld", &w[i]);
  for (int i = 2; i <= n; i++) {
    int p;
    scanf("%d", &p);
    add(p, i);
    fa[i] = p;
  }

  dfs1(1, 0);
  dfs2(1, 1);
  build(1, 1, n);

  scanf("%d", &m);
  while (m--) {
    int op, u, v;
    long k;
    scanf("%d%d", &op, &u);
    if (op == 1) {
      root = u;
    } else if (op == 2) {
      scanf("%d%ld", &v, &k);
      add_path(u, v, k);
    } else if (op == 3) {
      scanf("%ld", &k);
      add_tree(u, k);
    } else if (op == 4) {
      scanf("%d", &v);
      printf("%ld\n", query_path(u, v));
    } else printf("%ld\n", query_tree(u));
  }
}

预处理时间复杂度 O ( n ) O(n) O(n),每次询问时间 O ( log ⁡ 2 n ) O(\log^2n) O(log2n),空间 O ( n ) O(n) O(n)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值