给你一颗 \(n\) 个点的树,每一个点的度数不超过 \(20\) ,有 \(q\) 次修改点权的操做。node
须要动态维护带权重心,也就是找到一个点 \(v\) 使得 \(\displaystyle \sum_{v} w_v \times \mathrm{dist}(u, v)\) 最小。c++
\(n \le 10^5, q \le 10^5, \forall v, w_v \ge 0\)git
\(\text{Update on 2019.3.29:}\) 彷佛能够二叉化就能够不用保证度数了。。数据结构
首先了解一个重心的重要性质:大数据
对于一条边 \(x \to y\) 若是 \(x\) 侧子树和 \(>\) \(y\) 侧子树和,那么重心必定在 \(x\) 侧。优化
值得一提的是这个结论对于 \(\mathrm{dist}^k(x, i)\) 都是成立的,通常状况下都须要快速求出树的带权重心。spa
利用这个结论能够快速求出 带权重心 。debug
暴力求的话,在随机数据下表现很是优秀,可是咱们明显能够用一些数据结构来优化这个过程,此时不难想到 点分树 。code
由于对于上面那个找 带权重心 的过程,咱们能够考虑分治解决。get
这样咱们最多重复 \(\log n\) 次操做就停下来,接下来咱们就是须要动态求一个子树的 \(sum_y\) 。
这个显然能够用树剖线段树等数据结构进行维护,但咱们有了点分树显然这样是多余的。
咱们考虑对于每一个点分树上每一个点,维护它子树全部点的 \(w_i\) 的和,记为 \(\displaystyle sum_i = \sum_{v \in child(i)} w_v\) 。
咱们每次从 \(x \to y\) 向下分治的时候,令 \(v\) 为 \(x \to y\) 在 原树 路径上除 \(x\) 外第一个点。
咱们考虑把 \(v \to y\) 在点分树路径上的全部 \(sum\) 加上 \(sum_x - sum_y\) 也就是 \(x\) 部分的点权。
至于这样为何是对的。简单说明下,这样就会对接下来全部须要算上 \(x\) 部分贡献的分治重心进行贡献。这样咱们就保证了全部要算上的点都是算上的正确的答案。
注意作完后须要减回来。
而后咱们找到了重心,考虑计算答案。
有两种方法。
第一种是相似于 「HNOI2015」开店 其中一个作法,利用知足差分的性质。
也就是 \(\displaystyle \sum _{i=1}^{n} w_i \mathrm{dist}(x, i) = \sum_{i=1}^{n} w_i(d_i + d_x) - 2 \sum_{i=1}^{n} w_i d_{lca(i, x)}\) 的特性。(此处 \(d_i\) 为 \(i\) 的深度)
对于每一个点将其到根路径链上的点加上 \(w_i\) ,而后询问 \(x\) 到根的路径点权和 \(res\)。
用 \(\displaystyle \sum_{i=1}^{n} w_id_i + (\sum_{i=1}^{n}w_i)d_x\) 减去 \(2res\) 就好了。而后用树剖后,利用线段树就能够动态维护了。
但显然此处,咱们仍是有着点分树这个强大的树上结构,能够考虑换一种方式来维护。
咱们在以前维护 \(sum_u\) 的基础上,多维护两个东西。
而后询问 \(pos\) 节点的时候。咱们考虑每次在点分树向上跳,并计算贡献。
假设当前从 \(v \to u\) ,把 \(ans\) 加上 \((sum_{u} - sum_{v}) \times \mathrm{dist}(pos,u)\) ,这个意思就是把 \(v\) 外面全部的点加上这条边权的答案。
可是这样显然算少了,由于 \(v\) 外面全部点到 \(u\) 的距离没有算上,因此还要加上 \(tot_u - totfa_v\) 这部分贡献就好了。
注意 \(ans\) 一开始的时候初值是 \(tot_{pos}\) 。
而后为了代码没有那么毒瘤,对于此处的树上距离,咱们能够预处理出每一个点到它点分树上的祖先的距离,由于咱们只须要用上这些点对的距离。
对于一些动态有关树上距离的问题,咱们能够考虑点分树之类的强大数据结构。
而后带权重心均可以知足以前那个性质,能够用点分树上去找。
强烈建议看看个人代码!! 写的真的优秀!!
#include <bits/stdc++.h> #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << x << endl #define DEBUG(...) fprintf(stderr, __VA_ARGS__) using namespace std; inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;} inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;} inline int read() { int x = 0, fh = 1; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1; for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48); return x * fh; } void File() { #ifdef zjp_shadow freopen ("2135.in", "r", stdin); freopen ("2135.out", "w", stdout); #endif } const int N = 1e5 + 1e3, M = N << 1; typedef long long ll; int Head[N], Next[M], to[M], val[M], e; inline void add_edge(int u, int v, int w) { to[++ e] = v; Next[e] = Head[u]; Head[u] = e; val[e] = w; } inline void Add(int u, int v, int w) { add_edge(u, v, w); add_edge(v, u, w); } #define Travel(i, u, v) for(register int i = Head[u], v = to[i]; i; v = to[i = Next[i]]) bitset<N> vis; int sz[N], maxsz[N], rt, nodesum; void Get_Root(int u, int fa = 0) { sz[u] = maxsz[u] = 1; Travel(i, u, v) if (v != fa && !vis[v]) Get_Root(v, u), sz[u] += sz[v], chkmax(maxsz[u], sz[v]); chkmax(maxsz[u], nodesum - sz[u]); if (maxsz[u] < maxsz[rt]) rt = u; } ll dis[N][20]; int from[N], cur[N]; void Get_Dis(int u, ll dep, int fa, int anc) { if (fa) from[u] = anc, dis[u][cur[u] ++] = dep; Travel(i, u, v) if (!vis[v] && v != fa) Get_Dis(v, dep + val[i], u, anc); } typedef pair<int, ll> PII; #define fir first #define sec second #define mp make_pair vector<PII> Sub[N]; void Dfs_Div(int u = 1) { vis[u] = true; Get_Dis(u, 0, 0, u); Travel(i, u, v) if (!vis[v]) rt = 0, nodesum = sz[v], Get_Root(v), Sub[u].push_back(mp(rt, v)), from[rt] = u, Dfs_Div(rt); } ll sum[N], tot[N], tot_fa[N]; inline void Update(int pos, int uv) { tot_fa[pos] += dis[pos][0] * uv; for (register int u = pos, dep = 0; u; u = from[u], ++ dep) { sum[u] += uv; tot[from[u]] += dis[pos][dep] * uv; tot_fa[from[u]] += dis[pos][dep + 1] * uv; } } PII cache[N]; int len = 0; ll Sum; int Find_Root(int u) { for (PII it : Sub[u]) { register int v = it.fir; if (sum[v] * 2 > Sum) { register int pos; ll sumu; for(pos = it.sec, sumu = Sum - sum[v]; pos != from[u]; pos = from[pos]) sum[pos] += sumu, cache[++ len] = mp(pos, sumu); return Find_Root(v); } } return u; } int bas; inline ll Query() { register int pos = Find_Root(bas); For (i, 1, len) sum[cache[i].fir] -= cache[i].sec; len = 0; ll res = tot[pos]; for (register int u = from[pos], Last = pos, dep = 0; u; u = from[Last = u], ++ dep) res += tot[u] - tot_fa[Last] + (sum[u] - sum[Last]) * dis[pos][dep]; return res; } signed main () { File(); int n = read(), q = read(); For (i, 1, n - 1) { int u = read(), v = read(), w = read(); Add(u, v, w); } maxsz[rt = 0] = nodesum = n; Get_Root(1); Dfs_Div(bas = rt); For (i, 1, n) if(cur[i]) reverse(dis[i], dis[i] + cur[i]); while (q --) { int pos = read(), uv = read(); Sum += uv; Update(pos, uv); printf ("%lld\n", Query()); } return 0; }