給你一顆 \(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; }