树的基本性质(8个经典模板套路)

树的基本性质——前置知识

1. 树的性质

树的基本性质:
  • 树的边数:对于一个有 nnn 个节点的树,其边数为 n−1n - 1n1 条。这个性质可以通过归纳法或者集合论进行证明,假设从一个树开始,如果添加一条边形成一个环,那么就不再是树了。
  • 连通性:树是一种连通无环的图,即任意两节点之间都有一条唯一的路径。

2. 深度优先搜索 (DFS)

深度优先搜索的基本步骤:

  • 邻接表存图:通过邻接表存储图的结构,即每个节点有一个列表,存储它所有相邻的节点。
  • 记录深度:在树的遍历中,记录每个节点到根节点的距离(深度)。
示例代码:
const int N = 100010;
vector<int> G[N];
int n, dis[N];

void dfs(int u, int f) {
    for(int i = 0; i < G[u].size(); i++) {
        int v = G[u][i];
        if(v == f) continue;
        dis[v] = dis[u] + 1;
        dfs(v, u);
    }
}

解析:

  • NNN 是最大节点数。
  • GGG 是邻接表,用来存储图。
  • dis[u] 记录节点 uuu 的深度。
  • dfs(u, f) 以节点 uuu 为起点,fff 为父节点进行深度优先搜索。

3. 广度优先搜索 (BFS)

广度优先搜索的基本步骤:

  • 邻接表存图:同样使用邻接表存图。
  • 记录深度:通过一个队列来依次遍历图的节点,并记录每个节点到起点的距离。
示例代码:
const int N = 100010;
vector<int> G[N];
int n, dis[N], fa[N];
queue<int> q;

void bfs(int u) {
    q.push(u);
    while(!q.empty()) {
        int u = q.front();
        q.pop();
        for(int i = 0; i < G[u].size(); i++) {
            int v = G[u][i];
            if(v == fa[u]) continue;
            fa[v] = u;
            dis[v] = dis[u] + 1;
            q.push(v);
        }
    }
}

解析:

  • fa[u]fa[u]fa[u] 记录节点 uuu 的父节点。
  • dis[u]dis[u]dis[u] 记录节点 uuu 的距离。
  • BFS 通过队列从源节点开始层层扩展,访问每个节点的相邻节点。

4. 连通点集

定义:

对于每个节点,能通过其根节点向外扩展形成的子树数量,自己作为一个点也算一个子树。

示例代码:

void dfs1(int x, int fa) {
    for(int son : to[x]) {
        if(son == fa) continue;
        dfs1(son, x);
        dp1[x] = (dp1[x] * (dp1[son] + 1)) % mod;
    }
}

解析:

  • dp1[x]dp1[x]dp1[x] 表示以节点 xxx 为根的子树数。
  • 通过遍历每个儿子节点的子树数,累乘得到当前节点的子树数量。

5. 树的直径

定义:

树的直径是树上任意两节点之间最长的路径。

直径的性质:

  • 直径的两端点是叶子节点。
  • 从树上某个节点出发,找到距离最远的节点,这个节点一定是直径的一个端点。

示例代码:

const int N = 1e4 + 10;
int n, d = 0;
int d1[N], d2[N];
vector<int> e[N];

void dfs(int u, int fa) {
    d1[u] = d2[u] = 0;
    for(int v : e[u]) {
        if(v == fa) continue;
        dfs(v, u);
        int t = d1[v] + 1;
        if(t > d1[u]) {
            d2[u] = d1[u], d1[u] = t;
        } else if(t > d2[u]) {
            d2[u] = t;
        }
    }
    d = max(d, d1[u] + d2[u]);
}

解析:

  • d1[u]d1[u]d1[u] 为节点 uuu 向下的最长路径。
  • d2[u]d2[u]d2[u] 为节点 uuu 向下的次长路径。
  • ddd 为树的直径,即通过每个节点的最长路径和次长路径之和。

6. 两次 DFS 解法

解法步骤:

  • 首先从任意一个节点 uuu 开始,找到最远的节点 ccc,这一步的目的是找出树的一个端点。
  • 然后从节点 ccc 开始,进行一次 DFS,找到最远的节点,得到树的直径。

示例代码:

const int N = 1e4 + 10;
int n, c = 0;
int d[N];
vector<int> e[N];

void dfs(int x, int fa) {
    for(int v : e[x]) {
        if(v == fa) continue;
        d[v] = d[x] + 1;
        if(d[v] > d[c]) c = v;
        dfs(v, x);
    }
}

void solve() {
    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);
    d[c] = 0;
    dfs(c, 0);
    cout << d[c] << endl;
}

解析:

  • 通过两次 DFS,首先从任意节点开始,找到一个远点,然后从该远点出发找到最大距离,得到树的直径。

7. 树的重心

定义:

树的重心是指把树分成两部分时,某个点是两部分之间的平衡点,使得每一部分的节点数不超过整个树的一半。

示例代码:

const int N = 1e5 + 10;
int sz[N], w[N], cid[2];
int h[N], e[N], ne[N], idx;
int n;

void add(int u, int v) {
    e[++idx] = v, ne[idx] = h[u], h[u] = idx;
}

void dfs(int x, int fa) {
    sz[x] = 1;
    w[x] = 0;
    for(int i = h[x]; i != -1; i = ne[i]) {
        int v = e[i];
        if(v != fa) {
            dfs(v, x);
            sz[x] += sz[v];
            w[x] = max(w[x], sz[v]);
        }
    }
    w[x] = max(w[x], n - sz[x]);
    if(w[x] <= n / 2) {
        cid[cid[0] != 0] = x;
    }
}

解析:

  • sz[x]sz[x]sz[x] 为节点 xxx 的子树大小。
  • w[x]w[x]w[x] 为节点 xxx 作为根时,最大子树的大小。
  • 通过 DFS 计算每个节点的子树大小和最大子树大小,找出重心。

8. 树上的路径长度

问题描述:

计算树上长度恰好为 KKK 的路径数。

解法:

使用树的 DP 动态规划解法,记录每个节点到其他节点的距离,统计路径长度为 KKK 的路径。

示例代码:

const int N = 50010;
struct edge {
    int v, ne;
} e[N * 2];
int h[N], idx;
int f[N][510];
int n, m;
ll res = 0;

void add(int u, int v) {
    e[++idx] = {v, h[u]};
    h[u] = idx;
}

void dfs(int u, int fa) {
    f[u][0] = 1;
    for(int i = h[u]; i; i = e[i].ne) {
        int v = e[i].v;
        if(v == fa) continue;
        dfs(v, u);
        for(int j = 0; j < m; j++) {
            res += f[v][j] * f[u][m - j - 1];
        }
        for(int j = 0; j < m; j++) {
            f[u][j + 1] += f[v][j];
        }
    }
}

解析:

  • 通过树的 DFS 动态规划计算路径长度,f[u][j]f[u][j]f[u][j] 表示从节点 uuu 到深度为 jjj 的路径数,最终结果为路径长度为 KKK 的路径数。

通过以上解析,可以看出树相关的图论算法在解决很多复杂问题时非常高效,并且应用广泛。

posted @ 2025-08-01 00:22  晓律  阅读(51)  评论(0)    收藏  举报  来源