2020牛客暑期多校训练营(第七场)C.A National Pandemic

题面链接

思路:一个很显然的操作是把 w d i s ( x , y ) w-dis(x,y) 写成 w + d e p x + d e p y 2 d e p l c a ( x , y ) w+dep_x+dep_y-2dep_{lca(x,y)}
前三项都能用一个变量累加出来,那么主要问题就是如何较快的求 d e p l c a ( x , y ) dep_{lca(x,y)}
那我们可以对树进行轻重链剖分处理,对每条链维护两个bit。一个维护个数,一个维护dep的和。
每个update操作,从操作点开始往根跳,对沿途经过的链在bit上更新一下。
如下图所示:假设我们当前在4号点所在的链,我们先更新一下这条链的信息;
那么接下来我们会跳到10号点所在的链,再更新一下链的信息。
在这里插入图片描述
求答案的时候,我们也是跳链来累加答案,需要注意的是,在计算个数的时候有可能会重复计算,需要减掉重复的值。
可以结合代码来理解一下。。。
至于点分树的做法明天在学一下

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 5e4 + 10;
#define fi first
#define se second
#define pb push_back
#define wzh(x) cerr<<#x<<'='<<x<<endl;
int t, n, m, cnt;
int siz[N], fa[N], de[N], to[N], a[N], son[N];
vector<int>v[N];
void dfs1(int now, int pre, int d) {
  siz[now] = 1;
  fa[now] = pre;
  de[now] = d;
  int cnt = -1;
  for (auto k : v[now]) {
    if (k == pre)continue;
    dfs1(k, now, d + 1);
    siz[now] += siz[k];
    if (siz[k] > cnt) {
      cnt = siz[k];
      son[now] = k;
    }
  }
}
vector<int>link[N];
void dfs2(int now, int pre) {
  to[now] = pre;
  a[now] = ++cnt;
  link[pre].pb(now);
  if (!son[now])return ;
  dfs2(son[now], pre);
  for (auto k : v[now]) {
    if (k == fa[now] || k == son[now])continue;
    dfs2(k, k);
  }
}
LL laz[N];
int num[N];
vector<LL>bit[N], ti[N];
void add(int x, int y) {
  int z = ti[x].size();
  for (; y < z; y += y & -y)ti[x][y]++;
}
int ge(int x, int y) {
  int z = 0;
  for (; y > 0; y -= y & -y)z += ti[x][y];
  return z;
}
void add(int x, int y, int w) {
  int z = bit[x].size();
  for (; y < z; y += y & -y)bit[x][y] += w;
}
LL get(int x, int y) {
  LL z = 0;
  for (; y > 0; y -= y & -y) {
    z += bit[x][y];
  }
  return z;
}
void gao(int l){
  while (l) {
    add(to[l], num[l], de[l]);//维护dep和
    add(to[l], num[l]);//维护个数
    l = fa[to[l]];
  }
}
LL get(int l) {
  int pre = 0;
  LL res = 0;
  while (l) {
    res += 1ll * (ge(to[l], num[l]) - pre ) * de[l];//bit上到当前点的前缀和(维护个数的) 去掉重复的
    pre = ge(to[l], num[to[l]]);//下一次计算的时候重复的显然就是此是这个个链内出现的总数
    if (l != to[l]) {
      res += get(to[l], num[to[l]]) - get(to[l], num[l]);//当前点到top节点的 dep之和
    }
    l = fa[to[l]];
  }
  return res;
}
LL ans[N];
int main() {
  for (scanf("%d", &t); t; t--) {
    scanf("%d%d", &n, &m); cnt = 0;
    for (int i = 1; i <= n; i++) {
      v[i].clear();
      laz[i] = 0;
      ans[i] = 0;
      link[i].clear();
      son[i] = 0;
    }
    for (int i = 1; i < n; i++) {
      int x, y;
      scanf("%d%d", &x, &y);
      v[x].pb(y);
      v[y].pb(x);
    }
    dfs1(1, 0, 0);
    dfs2(1, 1);
    for (int i = 1; i <= n; i++) {
      int x = link[i].size();
      for (int j = 0; j < x; j++) {
        num[link[i][j]] = x - j;
      }
      bit[i].resize(x + 1);
      ti[i].resize(x + 1);
      for (int j = 0; j <= x; j++) {
        bit[i][j] = ti[i][j] = 0;
      }
    }
    LL w = 0;
    LL dep = 0;
    int cou = 0;
    for (int i = 1; i <= m; i++) {
      int ty;
      scanf("%d", &ty);
      if (ty == 1) {
        int dx, dw;
        scanf("%d%d", &dx, &dw);
        w += dw;
        cou++;
        dep += de[dx];
        gao(dx);
      } else if (ty == 2) {
        int dx;
        scanf("%d", &dx);
        LL res = get(dx);
        res = w - (dep + 1ll * cou * de[dx] - 2 * res);
        if (res + ans[dx] <= 0) {
          continue;
        } else {
          ans[dx] = -(res);//小于0,那么ans必然是等于此时的-res
        }
      } else {
        int dx;
        scanf("%d", &dx);
        LL res = get(dx);
        res = w - (dep + 1ll * cou * de[dx] - 2 * res);
        printf("%lld\n", res + ans[dx]);
      }
    }
  }
  return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_40655981/article/details/107738723