Loading

【图论】【数据结构】树的直径、树的中心、树的重心

树的直径

树上 2 个点距离最远的点形成的简单路径。

性质:

  1. 如果所有边权都为正数,那么所有直径的重点都是一个点,那个点就是树的中心
  2. 树上任意点 \(x\) 距离其最远的点一定是树的直径的端点。
  3. 树的直径的端点一定是度数为 1 的点。

SPOJ PT07Z, Longest path in a tree

两遍 dfs 求树的直径(中心)

#include <bits/stdc++.h>

using namespace std;

const int N = 10010;

vector<int> e[N];
int n;
int dep[N];

void dfs(int u, int fa) {
    for (int to : e[u]) {
        if (to == fa) continue;
        dep[to] = dep[u] + 1;
        dfs(to, u);
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        e[u].push_back(v);
        e[v].push_back(u);
    }

    dfs(1, 0);
    int maxver = -1, maxx = -0x3f3f3f3f;
    for (int i = 1; i <= n; i++) {
        if (maxx < dep[i]) {
            maxx = dep[i];
            maxver = i;
        }
    }

    memset(dep, 0, sizeof(dep));
    dfs(maxver, 0);

    cout << *max_element(dep + 1, dep + n + 1) << '\n';

    return 0;
}

树性 dp 求树的直径(中心):·求出每个点往下走的最长链和次长链,答案就是所有的点的最长链和次长链的长度之和的最大值。

#include <bits/stdc++.h>

using namespace std;

const int N = 10010, M = 20010;

struct edge {
    int to, next;
} e[M];

int head[N], idx = 1;

void add(int u, int v) {
    idx++, e[idx].to = v, e[idx].next = head[u], head[u] = idx;
}

int fzmax[N], fcmax[N];

void dfs(int u, int fa) {
    for (int i = head[u]; i; i = e[i].next) {
        int to = e[i].to;
        if (to == fa) continue;
        dfs(to, u);
        int d = fzmax[to] + 1;
        if (d > fzmax[u]) {
            fcmax[u] = fzmax[u];
            fzmax[u] = d;
        }
        else if (d > fcmax[u]) fcmax[u] = d;
    }
}

int n;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        add(u, v), add(v, u);
    }
    memset(fzmax, 0, sizeof(fzmax));
    memset(fcmax, 0, sizeof(fcmax));
    dfs(1, 0);
    int ans = 0;
    for (int i = 1; i <= n; i++) ans = max(ans, fzmax[i] + fcmax[i]);
    cout << ans << '\n';
    return 0;
}

例题

CF911F Tree Destruction

先标记树的直径,然后将除了直径之外的点删除,将它们到直径两端的最大值累加到答案中。最后将直径一点点删掉就可以了。

#include <bits/stdc++.h>

using namespace std;
using i64 = long long;

const int N = 200010;
const i64 INF = 0x3f3f3f3f;

int n;
vector<int> e[N];
int dep1[N], dep2[N];
int f[N];
bool path[N];
int out[N];

void dfs(int u, int fa, int *dep) {
    f[u] = fa;
    for (int to : e[u]) {
        if (to == fa) continue;
        dep[to] = dep[u] + 1;
        dfs(to, u, dep);
    }
}

i64 res;
vector<pair<int, pair<int, int> > > ans;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        e[u].push_back(v);
        e[v].push_back(u);
        out[u]++, out[v]++;
    }
    dfs(1, 0, dep1);
    int maxx = -INF, root = -1;
    for (int i = 1; i <= n; i++) {
        if (maxx < dep1[i]) {
            maxx = dep1[i];
            root = i;
        }
    }
    memset(dep1, 0, sizeof(dep1));
    dfs(root, 0, dep1);
    int maxver = -1;
    maxx = -INF;
    for (int i = 1; i <= n; i++) {
        if (maxx < dep1[i]) {
            maxx = dep1[i];
            maxver = i;
        }
    }

    int v = maxver;
    while (v != f[root]) {
        path[v] = true;
        v = f[v];
    }
    
    dfs(maxver, 0, dep2);

    queue<int> q;
    for (int i = 1; i <= n; i++) {
        if (path[i]) continue;
        if (out[i] == 1) {
            // cout << i << endl;
            q.push(i);
        }
    }

    while (q.size()) {
        int t = q.front();
        q.pop();
        if (path[t]) continue;

        for (int to : e[t]) {
            if (path[to]) continue;
            out[to]--;
            if (out[to] == 1) q.push(to);
        }

        if (dep1[t] < dep2[t]) {
            res += dep2[t];
            ans.push_back({maxver, {t, t}});
        }
        else {
            res += dep1[t];
            ans.push_back({root, {t, t}});
        }
    }

    v = root;
    while (v != maxver) {
        res += dep2[v];
        ans.push_back({maxver, {v, v}});
        v = f[v];
    }
    cout << res << '\n';
    for (auto x : ans) cout << x.first << ' ' << x.second.first << ' ' << x.second.second << '\n';
    return 0;
}

树的重心

树的重心

定义:对于一棵树形结构,若节点 \(x\) 为根节点时,其最大子树最小,则 \(x\) 称为树的重心。

性质:

  1. 树上结点到重心 \(x\) 的距离之和是最小的。
  2. 树的重心至多两个且一定相邻。
  3. 若树删除(增加) \(1\) 个叶子节点,则重心至多偏移 \(1\) 位。
  4. 若两颗树的重心分别为 \(x, y\) 那么任意在两棵树之间连边新的重心一定在 \(x\)\(y\) 的路径上。
  5. 树的重心为根节点时,其最大的子树一定不超过 \(\frac{n}{2}\),其中 \(n\) 为重点总数。

代码

void get_root(int u, int fa) {
	sz[u] = 1;
	int s = 0;
	for (int i = head[u]; i; i = e[i].next) {
		int to = e[i].to;
		if (to == fa) continue;
		get_root(to, u);
		s = max(s, sz[to]);
		sz[u] += sz[to];
	}
	s = max(s, sum - sz[u]);
	if (s <= sum / 2) {
		root = u;
	}
}

例题

MyBlog: HDU6567 Cotree

MyBlog: CF708C Centroids

MyBlog: CF685B Kay and Snowflake

MyBlog: CF1406C Link Cut Centroids

posted @ 2024-07-20 08:22  SunnyYuan  阅读(41)  评论(0编辑  收藏  举报