树的直径(树的最长路径)
洛谷 U81904
简单来说 树的直径就是树中最长的一条链
求法如下:
任取一点作为起点 找到距离该点最远的一个点u(DFS)
再找到距离u最远的一点v
u v之间的路径就是一条直径
细节如下:
对于每一个节点 我们关注挂在该点上的最长链
最长链的值的等于
从该点往下延伸至叶节点的
最长路径的值 加 次长路径的值
在DFS中 对于位置较深的点
挂在该点的最长链会先被计算
对于浅一层(高度更高)的点
只需要对(每一个子节点的最长链值+该点到对应子节点权值)求最大值即可
然后返回到更高层
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 5e5 + 10, M = N * 2;//无向边要乘2
int h[N], e[M], w[M], ne[M], idx;
int n, ans;
//任取一点作为起点 找到距离该点最远的一个点u(DFS)
//再找到距离u最远的一点v
//u v之间的路径就是一条直径
//对于有边权的树 直径是 权值和最大的一条链
//对于无边权的树(相当于每条边权重都为1) 直径是 经过点数最多的一条链子
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
//记录father确保dfs只会从上层往下层搜索
int dfs(int u, int father)
{
int dist = 0;//从当前点往下走的最大长度
int d1 = 0, d2 = 0;//分别表示最长距离和第二长距离
//初始化为0覆盖了负数的情况 对答案无影响
//d1+d2即为"挂在当前点上"的最长一条链
for (int i = h[u]; ~i; i = ne[i])
{
//对于每一个子节点继续进行搜索
//当前点记录为子节点的父亲
//防止后续的搜索搜回当前点
int j = e[i];
if (j == father)continue;
//w[i]意为当前点到编号为i的子节点的距离
int d = dfs(j, u) + w[i];
dist = max(dist, d);
//次大变最大 最大更新成更大的
if (d >= d1)d2 = d1, d1 = d;
else if (d > d2)d2 = d;//或者仅更新次大值
}
//直径是所有d1+d2的最大值
ans = max(d1 + d2, ans);//d1+d2即为"挂在当前点上"的最长一条链
return dist;
}
int main()
{
cin >> n;
//初始化邻接表表头
memset(h, -1, sizeof h);
for (int i = 1; i <= n - 1; i++)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
//不妨取1号开始计算 把1号点"拎起来"成为最高的点
//没有父节点 记作-1
//从上往下遍历的时候 要确保只能往下走不能往上走
//不然会重复搜索死循环(因为建立的是双向边)
dfs(1, -1);
cout << ans << endl;
return 0;
}
树的中心
在树中找到一个点A
使得点A到树中相对点A的最远点的距离最小
如果模仿上面 树的直径 的做法 把每一个点拎起来作为树根
求出向下走的最远距离 相当于n轮DFS 很容易TLE
于是产生新的解法如下:
随机选取一个u点 分为两类情况
第一类 从u点向下走的最远距离 用d1[u]表示
第二类 从u点向上走的最远距离 用up[u]表示
这样 从u点到相对最远点的距离就是 max(d1[u],up[u])
把树上每一个点依次看做u
最终得到答案 min(max(d1[i],up[i]))
对于第一类 自下而上递推 得到答案 和之前 树的直径 做法一致(DFS)
对于第二类 通过u点实现对j(u的子节点)的up[j]计算
如果j处于down1[u]的路径上
那么对于up[j]的计算只能使用down2[u]
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e4 + 10;
const int M = N * 2;
const int INF = 1e9;
int h[N], e[M], w[M], ne[M], idx;
//从该点往下走的最长路径与次长路径
int down1[N], down2[N];
//记录最长路径要从哪一个点(倒数第二个点)上来
int p1[N];
//从该点往上走的最长路径
int up[N];
int n;
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
int dfs_d(int u, int father)
{
down1[u] = down2[u] = -INF;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == father)continue;//不允许往上(往回)搜索
int d = dfs_d(j, u) + w[i];
if (d > down1[u])
{
down2[u] = down1[u], down1[u] = d;
p1[u] = j;
}
else if (d > down2[u])down2[u] = d;
}
//如果值没有被更新 说明这是叶子节点
if (down1[u] == -INF)down1[u] = down2[u] = 0;
return down1[u];
}
void dfs_u(int u, int father)
{
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == father)continue;
//通过u点实现对j(u的子节点)的up[j]计算
//如果j处于down1[u]的路径上
//那么对于up[j]的计算只能使用down2[u]
if (p1[u] == j)up[j] = max(up[u], down2[u]) + w[i];
else up[j] = max(up[u], down1[u]) + w[i];
dfs_u(j, u);
}
}
int main()
{
cin >> n;
memset(h, -1, sizeof h);
for (int i = 0; i < n - 1; i++)
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
dfs_d(1, -1);//第一类 向下走 和 树的直径 求法一样
dfs_u(1, -1);//第二类 再求往上走的最长路径
//枚举每个点
int res = INF;
for (int i = 1; i <= n; i++)res = min(res, max(down1[i], up[i]));
cout << res << endl;
return 0;
}
树的重心
树的重心相对简单
在树中找到一个结点
如果将这个点删除
剩余的 各个连通块的点数的 最大值最小
也有相关定义
对于树上的每一个点 计算其所有子树中最大的子树节点数
这个值最小的点就是这棵树的重心。
这里以及下文中的“子树”都是指无根树的子树
即包括“向上”的那棵子树 并且不包括整棵树自身
思路如下:
对于每一个点 求其所有子树的最大值(结点个数)
再对每一个点产生的最大值求最小值即为答案
任取树上一点u
对于分析所有子树的结点数 分为两类情况
第一类 是u的子树(在u的下部分)
第二类 是u的上部分(全部)
用DFS遍历树上的每一个点作为u
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
const int M = 2 * N;
int h[N], e[M], ne[M], idx;
bool st[N];//经典DFS判重 防止重复搜索
int ans = N;
int n;
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
//返回以u为根的子树大小
int dfs(int u)
{
//标记当前这个点已经被搜过
st[u] = true;
//sum:以u为根的子树结点数
//size:u的最大子树的结点数
int sum = 1;//从1开始 u点算在内
int size = 0;//去掉这个点后各个联通块的最大值
//遍历u的所有出边
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j])
{
//s是以j为根的子树结点数
int s = dfs(j);
//size记录u的最大子树结点数
size = max(size, s);
//累加u的各个子树结点数
sum += s;
}
}
//求点u的最大子树
//size是u的下部分
//n-sum是u的上部分
size = max(size, n - sum);
ans = min(ans, size);
//返回以u为根的子树结点数
return sum;
}
int main()
{
cin >> n;
memset(h, -1, sizeof h);
for (int i = 0; i < n; i++)
{
int a, b;
cin >> a >> b;
add(a, b), add(b, a);
}
//DFS遍历了树上的每一个点
dfs(1);
cout << ans << endl;
return 0;
}