动态带权重心 学习笔记
yukimianyan · · 算法·理论
定义及基本性质
定义:在一棵点带权(权值非负,以下省略此前提)的树上,如果在树中选择某个节点并删除,这棵树将分为若干棵子树,统计子树点权和并记录最大值。取遍树上所有节点,使此最大值取到最小的节点被称为整个树的带权重心。点不带权的树的重心就是点权都为
结论 1[^3]:点带正权的树的带权重心如果不唯一,则至多有两个,且这两个带权重心相邻。点带零权时,带权重心可能有很多。
结论 2[^3]:以树的带权重心为根时,所有子树的点权和都不超过整棵树点权和的一半。
引入边权
结论 3[^1]:使得从树上的任意一点出发到树上所有点的距离乘上目标点的点权和最小的起点一定是树的带权重心。 即带权重心是使
证明:假设使得答案最小的起点是
注意,带权重心只和点权有关而和边权无关。
结论 4:枚举所有
结论 3 中引入的
第一种做法:点分树
同上。复杂度可以做到
第二种做法:拆为链加链求和
记
最后一个和式的维护方法:对于每个点,将其到根的链上所有边加上点权乘以边权,最后计算出
代码
以下是 P3345[^5] 的代码,使用“动态带权重心“的做法 2 和“计算
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, __VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
using LL = long long;
constexpr int N = 1e5 + 10;
struct fenwick {/*{{{*/
LL t[N];
void add(int x, LL k) {
for (; x < N; x += x & -x) t[x] += k;
}
LL query(int x) {
LL r = 0;
for (; x > 0; x -= x & -x) r += t[x];
return r;
}
int binary(LL k) {
int p = 0;
for (int j = __lg(N); j >= 0; j--) {
int q = p | 1 << j;
if (q < N && k > t[q]) k -= t[p = q];
}
return p + 1;
}
} fen;/*}}}*/
int n, dfn[N], rnk[N], cnt, siz[N], q;
int fa[N], lb[N], dep[N];
vector<pair<int, int>> tr[N];
int upc[N];
namespace seg {/*{{{*/
LL ans[N << 2], len[N << 2], tag[N << 2];
void spread(int p, LL k) {
tag[p] += k, ans[p] += k * len[p];
}
void maintain(int p) {
ans[p] = ans[p << 1] + ans[p << 1 | 1];
}
void pushdown(int p) {
spread(p << 1, tag[p]);
spread(p << 1 | 1, tag[p]);
tag[p] = 0;
}
void build(int p, int l, int r) {
tag[p] = 0;
if (l == r) return len[p] = upc[rnk[l]], ans[p] = 0, void();
int mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
maintain(p);
len[p] = len[p << 1] + len[p << 1 | 1];
}
void modify(int ql, int qr, LL k, int p, int l, int r) {
if (ql <= l && r <= qr) return spread(p, k);
int mid = (l + r) >> 1;
pushdown(p);
if (ql <= mid) modify(ql, qr, k, p << 1, l, mid);
if (mid < qr) modify(ql, qr, k, p << 1 | 1, mid + 1, r);
maintain(p);
}
LL query(int ql, int qr, int p, int l, int r) {
if (ql <= l && r <= qr) return ans[p];
int mid = (l + r) >> 1;
pushdown(p);
LL res = 0;
if (ql <= mid) res += query(ql, qr, p << 1, l, mid);
if (mid < qr) res += query(ql, qr, p << 1 | 1, mid + 1, r);
return res;
}
};/*}}}*/
LL dis[N];
int son[N], top[N];
void dfs(int u, int p) {
siz[u] = 1, son[u] = 0;
int q = lb[p];
dep[u] = dep[fa[u] = p] + 1;
lb[u] = dep[p] - dep[q] != dep[q] - dep[lb[q]] ? p : lb[q];
for (auto [v, w] : tr[u]) if (v != p) dis[v] = dis[u] + w, dfs(v, u), siz[u] += siz[v], upc[v] = w, siz[son[u]] < siz[v] && (son[u] = v);
}
void cut(int u, int topf) {
dfn[u] = ++cnt, rnk[cnt] = u, top[u] = topf;
if (son[u]) cut(son[u], topf);
for (auto [v, w] : tr[u]) if (v != fa[u] && v != son[u]) cut(v, v);
}
void tc_add(int x, int y) {
while (x) {
seg::modify(dfn[top[x]], dfn[x], y, 1, 1, n);
x = fa[top[x]];
}
}
LL tc_query(int x) {
LL res = 0;
while (x) {
res += seg::query(dfn[top[x]], dfn[x], 1, 1, n);
x = fa[top[x]];
}
return res;
}
int main() {
#ifndef LOCAL
cin.tie(nullptr)->sync_with_stdio(false);
#endif
cin >> n >> q;
for (int i = 1, u, v, w; i < n; i++) {
cin >> u >> v >> w;
tr[u].emplace_back(v, w);
tr[v].emplace_back(u, w);
}
cnt = 0, dfs(1, 0), cut(1, 1);
LL ans1 = 0, ans2 = 0;
seg::build(1, 1, n);
while (q--) {
int x, y;
cin >> x >> y;
ans1 += y;
ans2 += dis[x] * y;
fen.add(dfn[x], y);
tc_add(x, y);
auto sv = fen.query(n);
int u = rnk[fen.binary((sv + 1) / 2)];
debug("u0 = %d; ", u);
auto piv = sv / 2;
auto qsub = [&](int x) {
return fen.query(dfn[x] + siz[x] - 1) - fen.query(dfn[x] - 1);
};
while (qsub(u) <= piv) u = lb[u] && qsub(lb[u]) <= piv ? lb[u] : fa[u];
debug("u = %d; ", u);
cout << ans1 * dis[u] + ans2 - 2 * tc_query(u) << endl;
}
return 0;
}
[^1]: 关于单点修改,查询树的带权重心的研究 - 洛谷专栏 [^2]: 斜二进制 LCA - 洛谷专栏 [^3]: 树的重心 - OI Wiki [^4]: P4211 LNOI2014 LCA - 洛谷 [^5]: P3345 ZJOI2015 幻想乡战略游戏 - 洛谷