洛谷 P3345 [ZJOI2015]幻想乡战略游戏 动态树分治

题目描述

傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来,更别说和别人打仗了。

在打仗之前,幽香现在面临一个非常基本的管理问题需要解决。 整个地图是一个树结构,一共有n块空地,这些空地被n-1条带权边连接起来,使得每两个点之间有一条唯一的路径将它们连接起来。

在游戏中,幽香可能在空地上增加或者减少一些军队。同时,幽香可以在一个空地上放置一个补给站。 如果补给站在点u上,并且空地v上有dv个单位的军队,那么幽香每天就要花费dv*dist(u,v)的金钱来补给这些军队。

由于幽香需要补给所有的军队,因此幽香总共就要花费为Sigma(Dv*dist(u,v),其中1<=V<=N)的代价。其中dist(u,v)表示u个v在树上的距离(唯一路径的权和)。

因为游戏的规定,幽香只能选择一个空地作为补给站。在游戏的过程中,幽香可能会在某些空地上制造一些军队,也可能会减少某些空地上的军队,进行了这样的操作以后,出于经济上的考虑,幽香往往可以移动他的补给站从而省一些钱。

但是由于这个游戏的地图是在太大了,幽香无法轻易的进行最优的安排,你能帮帮她吗? 你可以假定一开始所有空地上都没有军队。

输入输出格式

输入格式:
第一行两个数n和Q分别表示树的点数和幽香操作的个数,其中点从1到n标号。 接下来n-1行,每行三个正整数a,b,c,表示a和b之间有一条边权为c的边。 接下来Q行,每行两个数u,e,表示幽香在点u上放了e单位个军队(如果e<0,就相当于是幽香在u上减少了|e|单位个军队,说白了就是du←du+e)。数据保证任何时刻每个点上的军队数量都是非负的。

输出格式:
对于幽香的每个操作,输出操作完成以后,每天的最小花费,也即如果幽香选择最优的补给点进行补给时的花费。

输入输出样例

输入样例#1:
10 5
1 2 1
2 3 1
2 4 1
1 5 1
2 6 1
2 7 1
5 8 1
7 9 1
1 10 1
3 1
2 1
8 1
3 1
4 1
输出样例#1:
0
1
4
5
6
说明

对于所有数据,1<=c<=1000, 0<=|e|<=1000, n<=10^5, Q<=10^5 非常神奇的是,对于所有数据,这棵树上的点的度数都不超过20,且N,Q>=1

分析:
感觉真的很毒瘤啊。
题目要求带修改的树的带权重心。
考虑建出点分树,然后维护当前点的size及儿子的size,判断时先判断当前点是不是重心,如果不是再走向最大的儿子。

代码:

// luogu-judger-enable-o2
#include <cstdio>
#include <vector>

typedef long long i64;

const int N = 100000 + 10, E = N * 2, LOG = 20;

int n, tcase, root;
i64 tot = 0;

struct edge_t {
  int adj, cost;
  edge_t *next;
} *e[N], pool[E];

void link(int a, int b, int c) {
  static edge_t *tot = pool;
  tot->adj = b;
  tot->next = e[a];
  tot->cost = c;
  e[a] = tot++;
}

bool forbid[N];

int Centroid(int s) {
  static std::vector<int> q;
  static int size[N], fa[N];
  q.clear();
  q.push_back(s);
  fa[s] = 0;
  for (int i = 0; i < q.size(); ++i) {
    int a = q[i];
    size[a] = 1;
    for (edge_t *it = e[a]; it; it = it->next) {
      int b = it->adj;
      if (!forbid[b] && b != fa[a]) {
        q.push_back(b);
        fa[b] = a;
      }
    }
  }
  for (int i = q.size() - 1; i >= 0; --i) size[fa[q[i]]] += size[q[i]];
  int tot = size[s];
  for (int i = 0; i < q.size(); ++i) {
    int a = q[i];
    bool flag = (tot - size[a]) <= tot / 2;
    for (edge_t *it = e[a]; flag && it; it = it->next) {
      int b = it->adj;
      if (!forbid[b] && b != fa[a]) flag &= (size[b] <= tot / 2);
    }
    if (flag)
      return a;
  }
  return s;
}

int anc[N], from[N];
std::vector<int> succ[N];

int Divide(int a) {
  forbid[a = Centroid(a)] = true;
  for (edge_t *it = e[a]; it; it = it->next) {
    int b = it->adj;
    if (!forbid[b]) {
      int c = Divide(b);
      succ[a].push_back(c);
      anc[c] = a;
      from[c] = b;
    }
  }
  return a;
}

int fa[N], dep[N], left[N];
i64 sum[N], dist[N], f[N], sub[N];

std::vector<int> dfn, table[LOG];

void DFS(int a) {
  left[a] = dfn.size();
  dfn.push_back(a);
  for (edge_t *it = e[a]; it; it = it->next) {
    int b = it->adj;
    if (b != fa[a]) {
      fa[b] = a;
      dep[b] = dep[a] + 1;
      dist[b] = dist[a] + it->cost;
      DFS(b);
      dfn.push_back(a);
    }
  }
}

void BuildSparseTable() {
  table[0] = dfn;
  for (int i = 1; i < LOG; ++i) {
    table[i].resize(dfn.size());
    for (int j = 0; j + (1 << (i - 1)) <= dfn.size(); ++j) {
      int a = table[i - 1][j], b = table[i - 1][j + (1 << (i - 1))];
      table[i][j] = (dep[a] < dep[b] ? a : b);
    }
  }
}

inline int lg2(int x) { return 31 - __builtin_clz(x); }

int LCA(int a, int b) {
  a = left[a], b = left[b];
  if (a > b) std::swap(a, b);
  int layer = lg2(b - a + 1);
  int u = table[layer][a], v = table[layer][b - (1 << layer) + 1];
  return dep[u] < dep[v] ? u : v;
}

i64 Dist(int u, int v) { return dist[u] + dist[v] - 2 * dist[LCA(u, v)]; }

int FindCentroid(int a) {
  for (std::vector<int>::iterator it = succ[a].begin(); it != succ[a].end(); ++it) {
    int b = *it;
    if (sum[b] > tot / 2) {
      i64 out = sum[a] - sum[b];
      for (int t = from[b]; t; t = anc[t]) sum[t] += out;
      int res = FindCentroid(b);
      for (int t = from[b]; t; t = anc[t]) sum[t] -= out;
      return res;
    }
  }
  return a;
}

i64 Solve(int u, int e) {
  tot += e;
  for (int a = u; a; a = anc[a]) {
    sum[a] += e;
    if (anc[a]) sub[a] += e * Dist(u, anc[a]);
    f[a] += e * Dist(u, a);
  }
  int c = FindCentroid(root);
  i64 res = 0;
  for (int a = c, b = 0; a; b = a, a = anc[a]) {
    res += (f[a] - sub[b]) + (sum[a] - sum[b]) * Dist(c, a);
  }
  return res;
}

int main() {
  scanf("%d%d", &n, &tcase);
  for (int cnt = n - 1, a, b, c; cnt--;) {
    scanf("%d%d%d", &a, &b, &c);
    link(a, b, c);
    link(b, a, c);
  }
  root = Divide(1);
  DFS(1);
  BuildSparseTable();
  while (tcase--) {
    int u, e;
    scanf("%d%d", &u, &e);
    printf("%lld\n", Solve(u, e));
  }
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值