题目传送门

一、题意分析

  1. 树是一种图,图的问题都需要建图,建图无外乎三种办法:邻接矩阵、邻接表、链式前向星

  2. 什么树重心?

    重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。

    树的重心不一定只是一个,有时可以是两个。

  3. 如何求树的重心
    在树上可以进行两种遍历:深度、广度。其中深度遍历可以模拟一支笔在树上画线,直至遍历完成所有节点。我们可以利用这一特点,让每个大臣分派出任务时,都要求下一级的官员返回自己管辖范围内的结点数。然后它自己负责把下级返回的个数加在一起,再加上自己的结点数1,返回给上级调用者。

  4. 无向图就是存两个方向的有向图,比如无向图中两个节点\(a,b\), 我们就保存\(a->b\),同时也保存\(b->a\),就描述了无向图

  5. 如果是稠密图,用邻接矩阵,稀疏图用链式前向星或邻接表。

二、链式前向星I

#include <bits/stdc++.h>

using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 100010;
//答案
int ans = INF;           //剩余各个连通块中点数的最大值最小
bool st[N];              //是不是走过了

int n;
int h[N], e[N << 1], ne[N << 1], idx; 
//h[N]表示有N条单链表的头,e[M]代表每个节点的值,ne[M]代表每个节点的下一个节点号


//链式前向星
void add(int a, int b) {
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

//函数定义:以u为根的子树的节点数量
//dfs有一个特点,可以附加的带回这个子树有多少个节点
int dfs(int u) {
    //防止走回头路
    st[u] = true;
    int sum = 1, res = 0; //sum从1开始,因为自己这个点算第一个点
    //遍历一下当前点的所有出边
    for (int i = h[u]; i != -1; i = ne[i]) {
        int j = e[i];
        if (!st[j]) { //没走过
            int s = dfs(j); //发出任务,让j回答以它为根的节点数量
            sum += s;       //累加的节点数和
            res = max(res, s);//这个节点如果去掉的话,生成的子树节点最多的是多少
        }
    }
    res = max(res, n - sum); //反向建构把它删除后,其它的是不是比由它生成的多
    //哪个方案最少的最大是答案
    ans = min(ans, res);
    return sum;
}

int main() {
    //树的结点数
    cin >> n;
    //初始化为-1,每个头节点写成-1
    memset(h, -1, sizeof h);

    //读入数据,n个结点,n-1条边,建图
    for (int i = 1; i <= n - 1; i++) {
        int a, b;
        cin >> a >> b;
        add(a, b), add(b, a); //无向图,双向建边
    }
    // 从一号节点开始进行搜索,其实,树的封闭的,从哪个点出发都是一样的效果
    dfs(1);
    //输出
    cout << ans << endl;
    return 0;
}

三、链式前向星II

#include <bits/stdc++.h>

using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 100010;
int n, m, idx;          //n个点,m条边,idx是新结点加入的数据内索引号

//链式前向星
struct Edge {
    int to;     //到哪个结点
    int value;  //边权
    int next;   //同起点的下一条边的编号
} edge[N << 1]; //同起点的边的集合 N<<1就是2*N,一般的题目,边的数量通常是小于2*N的,这个看具体的题目要求

int head[N];    //以i为起点的边的集合入口处

//加入一条边,x起点,y终点,value边权
void add_edge(int x, int y, int value) {
    edge[++idx].to = y;         //终点
    edge[idx].value = value;    //权值
    edge[idx].next = head[x];   //以x为起点上一条边的编号,也就是与这个边起点相同的上一条边的编号
    head[x] = idx;              //更新以x为起点上一条边的编号
}

//答案
int ans = INF;           //剩余各个连通块中点数的最大值最小
bool st[N];              //是不是走过了


//函数定义:以u为根的子树的节点数量
//dfs有一个特点,可以附加的带回这个子树有多少个节点
int dfs(int u) {
    //防止走回头路
    st[u] = true;
    int sum = 1, res = 0; //sum从1开始,因为自己这个点算第一个点
    //遍历一下当前点的所有出边
    for (int i = head[u]; i; i = edge[i].next) {
        if (!st[edge[i].to]) { //没走过
            int s = dfs(edge[i].to); //发出任务,让j回答以它为根的节点数量
            sum += s;       //累加的节点数和
            res = max(res, s);//这个节点如果去掉的话,生成的子树节点最多的是多少
        }
    }
    res = max(res, n - sum); //反向建构把它删除后,其它的是不是比由它生成的多
    //哪个方案最少的最大是答案
    ans = min(ans, res);
    return sum;
}

int main() {
    //树的结点数
    cin >> n;

    //读入数据,n个结点,n-1条边,建图
    for (int i = 1; i <= n - 1; i++) {
        int a, b;
        cin >> a >> b;
        //加入到链式前向星
        add_edge(a, b, 1);
        //加入到链式前向星
        add_edge(b, a, 1);
    }
    // 从一号节点开始进行搜索,其实,树的封闭的,从哪个点出发都是一样的效果
    dfs(1);
    //输出
    cout << ans << endl;
    return 0;
}

四、邻接表

#include <bits/stdc++.h>

using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 100010;

struct Edge {       //记录边的终点,边权的结构体
    int to;         //终点
    int value;      //边权
};
int n, m; //表示图中有n个点,m条边
vector<Edge> p[N];  //使用vector的邻接表

//答案
int ans = INF;           //剩余各个连通块中点数的最大值最小
bool st[N];              //是不是走过了


//函数定义:以u为根的子树的节点数量
//dfs有一个特点,可以附加的带回这个子树有多少个节点
int dfs(int u) {
    //防止走回头路
    st[u] = true;
    int sum = 1, res = 0; //sum从1开始,因为自己这个点算第一个点
    //遍历一下当前点的所有出边
    for (int i = 0; i < p[u].size(); i++) {
        if (!st[p[u][i].to]) { //没走过
            int s = dfs(p[u][i].to); //发出任务,让j回答以它为根的节点数量
            sum += s;       //累加的节点数和
            res = max(res, s);//这个节点如果去掉的话,生成的子树节点最多的是多少
        }
    }
    res = max(res, n - sum); //反向建构把它删除后,其它的是不是比由它生成的多
    //哪个方案最少的最大是答案
    ans = min(ans, res);
    return sum;
}

int main() {
    //树的结点数
    cin >> n;

    //读入数据,n个结点,n-1条边,建图
    for (int i = 1; i <= n - 1; i++) {
        int a, b;
        cin >> a >> b;
        p[a].push_back({b, 1});
        p[b].push_back({a, 1});
    }
    // 从一号节点开始进行搜索,其实,树的封闭的,从哪个点出发都是一样的效果
    dfs(1);
    //输出
    cout << ans << endl;
    return 0;
}

相关文章: