[算法学习笔记] 树的常用处理方法
Trick:在递归中一定要开局部变量,全局变量每次递归都会改变,影响答案!
树的遍历
例题:树的重心
对树进行 dfs,处理每个节点作为重心的时候各个连通块点数最大值即可。
对于重心上面的连通块,可以用 \(n-\) 下面连通块点数和 \(-1\)。
实现
int dfs(int now)
{
vis[now] = 1;
int sum = 1,res = 0;
for(int i=0;i<Edge[now].size();i++)
{
int v = Edge[now][i];
if(!vis[v])
{
int t = dfs(v);
sum += t;
res = max(res,t);
}
}
res = max(res,n-sum);
if(res <= ans)
{
ans = res;
minn_num = now;
}
return sum;
}
求树的直径
法1:进行两次 dfs,任取一点先求出到它的最远距离,再从最远距离的那个点再跑一遍。
法2:树形 dp。显然直径上的点从它开始的最远距离和次远距离一定是直径的两个端点。求出每个节点开始走的最远距离和次远距离之和再取 max 即可。
在树形 dp 的时候还需要注意,从 \(u\) 点出发的最大距离点和次大距离点必须 不是由同一个 \(v\) 转化而来的。我们显然需要确保直径是简单路径。
这也很简单,在尝试直接更新次长路时,确保该 \(v\) 没有被最长路利用,else if 即可。
实现
树形 dp
void dfs(int now,int fa)
{
for(int i=0;i<Edge[now].size();i++)
{
int v = Edge[now][i];
if(v == fa) continue;
dfs(v,now);
if(d1[v] + 1 > d1[now])
{
d2[now] = d1[now];
d1[now] = d1[v] + 1;
}
else if(d1[v] + 1 > d2[now]) d2[now] = d1[v] + 1; //使用else if确保不重复
}
}
两遍 dfs
void dfs(int now,int fa,int dis)
{
for(int i=0;i<Edge[now].size();i++)
{
int v = Edge[now][i].first,w = Edge[now][i].second,u = now;
if(v == fa) continue;
dfs(v,now,dis+w);
}
int u = now;
d[u] = dis;
if(dis > tmp2)
{
tmp2 = dis;
tmp1 = u;
}
}
小应用:树的中心
常规思路这是一个换根 dp 板子,但只能用换根 dp 做吗?
结论: 树的中心一定在树的直径上。
证明:树的直径是树上的最长路径。而我们要求一个点在树上它到另一个点的最远距离最近。也就是直径上趋于中点。如果不在直径上则会走的更远(得先走到直径上)
因此,我们求出树的直径,接着求每个点距离树的直径两个端点的距离,取min即可。
实现
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
typedef pair<int,int> PAIR;
const int N = 10010;
const int INF = 0x3f3f3f3f;
vector <PAIR> Edge[N];
int d1[N],d2[N];
int res = INF;
int n;
int tmp1,tmp2;
int d[N];
void dfs(int now,int fa,int dis)
{
for(int i=0;i<Edge[now].size();i++)
{
int v = Edge[now][i].first,w = Edge[now][i].second,u = now;
if(v == fa) continue;
dfs(v,now,dis+w);
}
int u = now;
d[u] = dis;
if(dis > tmp2)
{
tmp2 = dis;
tmp1 = u;
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=1;i<n;i++)
{
int u,v,w;
cin>>u>>v>>w;
Edge[u].push_back(PAIR(v,w));
Edge[v].push_back(PAIR(u,w));
}
dfs(1,0,0);
int pos1 = tmp1;
tmp2 = 0,tmp1 = 0;
dfs(pos1,0,0);
int pos2 = tmp1;
tmp2 = 0,tmp1 = 0;
for(int i=1;i<=n;i++) d1[i] = d[i]; //距离第一个端点的距离,次长
dfs(pos2,0,0);
for(int i=1;i<=n;i++)d2[i] = d[i]; //距离第二个端点的距离,不用memset,也就是求最长
int ans = 0x3f3f3f3f;
for(int i=1;i<=n;i++)
{
if(ans > max(d1[i],d2[i])) ans = max(d1[i],d2[i]);
}
cout<<ans<<endl;
return 0;
}
可以发现很多题目都可以转换成直径相关!也算是一个小Trick吧(
本文作者:SXqwq,转载请注明原文链接:https://www.cnblogs.com/SXqwq/p/17655992.html
分类:
算法学习笔记&个人exp
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!