一、题意分析
-
树是一种图,图的问题都需要建图,建图无外乎三种办法:邻接矩阵、邻接表、链式前向星。
-
什么树重心?
重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。
树的重心不一定只是一个,有时可以是两个。
-
如何求树的重心
在树上可以进行两种遍历:深度、广度。其中深度遍历可以模拟一支笔在树上画线,直至遍历完成所有节点。我们可以利用这一特点,让每个大臣分派出任务时,都要求下一级的官员返回自己管辖范围内的结点数。然后它自己负责把下级返回的个数加在一起,再加上自己的结点数1,返回给上级调用者。 -
无向图就是存两个方向的有向图,比如无向图中两个节点(a,b), 我们就保存(a->b),同时也保存(b->a),就描述了无向图。
-
如果是稠密图,用邻接矩阵,稀疏图用链式前向星或邻接表。
二、链式前向星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;
}