树上的一条最长的路径。

→ 红色的部分是直径
→ 红色的部分才是直径
→ 反过来,对于任意顶点
时间
这个树的直径有哪些?
直径的个数?
直径的个数?
→ 从两个不同的子树中各选一个深度是
→ 由于虚线的限制,不通过树的中心的路径,长度不超过
直径的个数?
→ (左侧深度等于

重心只有一个
如果不是这样,是什么情形呢?
(这两个定义等价)
vector<int> g[maxn];
int sz[maxn];
void get_size(int u, int p) {
sz[u] = 1;
for (int v : g[u])
if (v != p) {
dfs(v, u);
sz[u] += sz[v];
}
}
int n;
int centroid(int u, int p) {
for (int v : g[u])
if (v != p && sz[v] * 2 > n)
return centroid(v, u);
return u;
}
洛谷P5666
给你一个有
删除一条边后,分裂成两个子树。这样的树有
求这些树的重心的编号之和。若一个树有两个重心,两个都算。
一个测试点有
设
对于有根树里的一个点
考虑以点
对于每个点
为此,我们分别两种情形
情况一:
需要有
情况二:
对于
vector<int> g[maxn];
int sz[maxn], h[maxn], h2[maxn];
void get_size(int u, int p) {
sz[u] = 1; h[u] = 0, h2[u] = 0;
for (int v : g[u]) {
if (v != p) {
get_size(v, u);
sz[u] += sz[v];
if (sz[v] > h[u]) {
h2[u] = h[u]; h[u] = sz[v];
} else if (sz[v] > h2[u])
h2[u] = sz[v];
}
}
}
对于我们要数的东西(计数对象),点
int a[maxn];
int n;
void add(int p, int v) {
while (p <= n) {
a[p] += v;
p += p & -p;
}
}
int sum(int p) {
if (p < 0) return 0;
if (p > n) p = n;
int ans = 0;
while (p > 0) {
ans += a[p];
p -= p & -p;
}
return ans;
}
int sum(int l, int r) {
return sum(r) - sum(l - 1);
}
对于每个点
这个列表是有序的,我们可以在上面二分查找。
vector<int> anc;
int cnt_anc(int L, int R) {//anc是一个递减的序列
return upper_bound(anc.rbegin(), anc.rend(), R)
- lower_bound(anc.rbegin(), anc.rend(), L);
}
int cnt[maxn]; // cnt[u]:点u做重心的次数。
void dfs(int u, int p) {
if (p) {
anc.push_back(sz[u]);
add(sz[u], 1);
}
// case 1
cnt[u] += cnt_anc(2 * h[u], 2 * sz[u]);
// case 2
int L = n - 2 * sz[u]; int R = n - 2 * h[u];
// 去掉u的祖先的中满足条件的v
cnt[u] -= cnt_anc(L, R);
for (int v : g[u]) {
if (v == p) continue;
int new_h = (sz[v] == h[u] ? max(h2[u], n - sz[u])
: max(h[u], n - sz[u]));
int new_sz = n - sz[v];
int new_L = n - 2 * new_sz; int new_R = n - 2 * new_h;
cnt[u] -= sum(new_L, new_R);
cnt[u] += sum(L, R);
dfs(v, u);
cnt[u] -= sum(L, R);
cnt[u] += sum(new_L, new_R);
}
if (p) anc.pop_back();
}
int main() {
int T; cin >> T;
while (T--) {
cin >> n;
for (int i = 1; i <= n; i++) {
g[i].clear();
cnt[i] = 0;
a[i] = 0;
}
for (int i = 0; i < n - 1; i++) {
int u, v; cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
get_size(1, 0);
dfs(1, 0);
long long ans = 0;
for (int u = 1; u <= n; u++) {
int L = n - 2 * sz[u];
int R = n - 2 * h[u];
cnt[u] += sum(L, R);
ans += (long long) u * cnt[u];
}
cout << ans << '\n';
}
}
重心到每个点的距离之和最小。
考虑一个有
其中
那么,当且仅当
证明?
有一个有

对于有根树,我们把点 u 的 size 子树 u 的 size 视作同义词。
u 是求和指标,k 是计数对象。