树的重心

性质

  1. 删去后剩下的连通块大小不超过原树节点数的一半
  2. 删去后剩下的连通块大小的最大值最小
  3. 所有点到其距离和最小
    这三个性质是重心的等价定义。
  4. 当节点数为奇数时树的重心只有一个,节点数为偶数时最多有两个,若有两个则两个相邻。
  5. 将两棵树用一条边相连,新树的重心在两颗原树重心的路径上。
  6. 添加或删除一个叶子,重心最多移动一条边的距离,添加时最多向所加叶子方向移动一条边的距离。
  7. 对于有根树:重心一定在根节点所在重链上,一棵树的重心一定是根节点重儿子的子树的重心的祖先。
  8. \(u\) 为根的子树,其重心一定是其到其重子树重心链上深度最大的,满足 \(2sz_v \ge sz_u\)\(v\)
    性质 \(4 \sim 8\) 都可以通过性质 \(1 \sim 3\) 得到。
    求法:dfs 记录子树大小,或者换根 DP 找到和所有点距离之和最小的点。

例题

CF685B

可以从每一颗子树的重心向上找,每个点最多判断一次,时间复杂度 \(\mathcal{O}(n)\)

namespace Loop1st {
int n, Q, ans[N], sz[N], fa[N], mx[N];
vector<int>e[N];
void dfs(int u) {
    sz[u] = 1; ans[u] = u;
    for (int v : e[u]) {
        dfs(v);
        sz[u] += sz[v];
        mx[u] = max(mx[u], sz[v]);
    }
    for (int v : e[u]) {
        int x = ans[v];
        while (x != u) {
            if (max(mx[x], sz[u] - sz[x]) <= (sz[u] >> 1)) { ans[u] = x; break; }
            else x = fa[x];
        }
    }
}
void main() {
    cin >> n >> Q;
    for (int i = 2; i <= n; i++) {
        cin >> fa[i];
        e[fa[i]].push_back(i);
    }
    dfs(1);
    while (Q--) {
        int u; cin >> u;
        cout << ans[u] << '\n';
    }
}

}

根据性质 \(8\) 也可以得到一个做法,时间复杂度 \(\mathcal{O}(n)\)

namespace Loop1st {
int n, Q, ans[N], sz[N], fa[N], son[N];
vector<int>e[N];
void dfs(int u) {
    sz[u] = 1;
    for (int v : e[u]) {
        dfs(v);
        sz[u] += sz[v];
        if (sz[v] > sz[son[u]]) son[u] = v;
    }
    if (!son[u]) { ans[u] = u; return ; }
    ans[u] = ans[son[u]];
    while ((sz[ans[u]] << 1) < sz[u]) ans[u] = fa[ans[u]];
}
void main() {
    cin >> n >> Q;
    for (int i = 2; i <= n; i++) {
        cin >> fa[i];
        e[fa[i]].push_back(i);
    }
    dfs(1);
    while (Q--) {
        int u; cin >> u;
        cout << ans[u] << '\n';
    }
}

}

CF708C

考虑一个点的所有子树(包括外面的子树),若有一个 \(> n/2\) 的子树(显然至多只有一个),那么可以考虑把这个子树里的某一棵 \(sz \le n/2\) 的子树割掉,挂在 \(u\) 上。

直接考虑比较困难,考虑一个 trick: 把原树的一个重心作为根,那么每个点原来的子树都 \(\le n/2\),于是只要考虑外子树,那么记外子树中的 \(sz \le n/2\) 且最大的子树大小为 \(cut_u\),判断 \(n - sz_u - cut_u\) 是否 \(\le n/2\) 即可。时间复杂度 \(\mathcal{O}(n)\)

namespace Loop1st {
int n, cg, fa[N], sz[N], cut[N], f[N][2], ans[N];
vector<int>e[N];
void dfs1(int u) {
    sz[u] = 1;
    int mx = 0;
    for (int v : e[u]) if (v != fa[u]) {
        fa[v] = u;
        dfs1(v);
        sz[u] += sz[v];
        mx = max(mx, sz[v]);
    }
    if (max(mx, n - sz[u]) <= (n >> 1)) cg = u;
}
void dfs2(int u) {
    sz[u] = 1;
    for (int v : e[u]) if (v != fa[u]) {
        fa[v] = u;
        dfs2(v);
        sz[u] += sz[v];
        if (sz[v] > (n >> 1)) continue;
        if (sz[v] > f[u][0]) f[u][1] = f[u][0], f[u][0] = sz[v];
        else if (sz[v] > f[u][1]) f[u][1] = sz[v];
    }
}
void dfs3(int u, int now) {
    cut[u] = now;
    for (int v : e[u]) if (v != fa[u]) {
        if (n - sz[u] <= (n >> 1)) now = max(now, n - sz[u]);
        if (f[u][0] == sz[v]) dfs3(v, max(now, f[u][1]));
        else dfs3(v, max(now, f[u][0]));
    }
    if (u == cg || n - sz[u] - cut[u] <= (n >> 1)) ans[u] = 1;
}
void main() {
    cin >> n;
    for (int i = 1, u, v; i < n; i++) {
        cin >> u >> v;
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs1(1);
    fa[cg] = 0;
    dfs2(cg);
    dfs3(cg, 0);
    for (int i = 1; i <= n; i++) cout << ans[i] << " \n"[i == n];
}

}

CSPS2019 树的重心

首先 \(n\) 为奇数所以只有一个重心 \(rt\),以它为根,对于 \(x \neq rt\),若 \(x\) 成为割掉某边后的重心,则这条边一定不在 \(x\) 子树内,设割掉后另一子树大小为 \(S\),记 \(g_x = \max_{y \in son_x} sz_y\),则有两个限制:

\[2 \times (n - S - sz_x) \le n - S \]

\[2 \times g_x \le n - S \]

得到:

\[n - 2sz_x \le S \le n - 2g_x \]

且要求边不在 \(x\) 子树内,数据结构维护即可。

\(x = rt\) 可以在 dfs 途中处理。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
// typedef __int128 i128;
typedef pair<int, int> pii;
const int N = 3e5 + 10, mod = 998244353;
template<typename T>
void dbg(const T &t) { cout << t << endl; }
template<typename Type, typename... Types>
void dbg(const Type& arg, const Types&... args) {
    #ifdef ONLINE_JUDGE
        return ;
    #endif
    cout << arg << ' ';
    dbg(args...);
}   
struct Query {
	int t, l, r;
};
int T, n, cg, sz[N], g[N], dfn[N], idx, m1, m2, in[N];
ll ans;
vector<int>e[N];
vector<Query>q[N];
struct Fenwick {
	int tr[N];
	#define lowbit(x) (x & -x)
	void clear() { for (int i = 1; i <= n; i++) tr[i] = 0; }
	void add(int x, int v) {
		if (!x) return ;
		while (x <= n) {
			tr[x] += v;
			x += lowbit(x);
		}
	}
	int qry(int x) {
		if (x < 0) return 0;
		int res = 0;
		while (x) {
			res += tr[x];
			x -= lowbit(x);
		}
		return res;
	}
	int ask(int l, int r) {
		if (l > r) return 0;
		return qry(r) - qry(l - 1);
	}
} t1, t2;
inline void dfs1(int u, int fa) {
	sz[u] = 1; g[u] = 0;
	dfn[u] = ++idx;
	for (auto v : e[u]) if (v != fa) {
		dfs1(v, u);
		sz[u] += sz[v];
		g[u] = max(g[u], sz[v]);
	}
	if (!cg && max(g[u], n - sz[u]) * 2 <= n) cg = u;
}
inline void dfs2(int u, int fa) {
	t1.add(sz[fa], -1);
	t1.add(n - sz[u], 1);
	if (u != cg) {
		int l = n - sz[u] * 2, r = n - g[u] * 2;
		ans += (ll)u * (t1.ask(l, r) + t2.ask(l, r));
		if (in[fa]) in[u] = 1;
		if (in[u]) ans += cg * (sz[u] <= n - sz[m2] * 2);
		else ans += cg * (sz[u] <= n - sz[m1] * 2);
	}
	t2.add(sz[u], 1);
    for (auto v : e[u]) if (v != fa) {
        dfs2(v, u);
    }
    t1.add(sz[fa], 1);
    t1.add(n - sz[u], -1);
    if (u != cg) {
		int l = n - sz[u] * 2, r = n - g[u] * 2;
		ans -= (ll)u * t2.ask(l, r);
	}
}
// 这里有个猎奇的点,就是 cg 为根的时候本来给 0 加一是会出问题的,但是这里由于 sz[0] = 0 恰好抵消了,当然我的意思是 xht 的写法是对的,因为 BIT 里给 x++ 了,但是不加还是会似 
int main() {
	// freopen("data.in", "r", stdin);
	// freopen("data.out", "w", stdout);
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    cin >> T;
    while (T--) {
    	ans = 0;
    	cin >> n;
    	cg = m1 = m2 = 0;
    	t1.clear(); t2.clear();
    	for (int i = 1; i <= n; i++) e[i].clear(), q[i].clear(), in[i] = 0;
    	for (int i = 1, u, v; i < n; i++) {
    		cin >> u >> v;
    		e[u].push_back(v); e[v].push_back(u);
		}
		dfs1(1, 0);
		idx = 0;
		dfs1(cg, 0);
		for (auto v : e[cg]) {
			if (sz[v] > sz[m1]) m2 = m1, m1 = v;
			else if (sz[v] > sz[m2]) m2 = v;
		}
		in[m1] = 1;
		for (int i = 1; i <= n; i++) t1.add(sz[i], 1);
		dfs2(cg, 0);
		cout << ans << '\n';
	}
    return 0;
}
posted @ 2026-04-06 21:44  循环一号  阅读(2)  评论(0)    收藏  举报