树
树
date : 2024.9.23
无根树
节点只有相邻关系而无父子关系
void dfs(int from,int x){
for x 的所有相邻节点 y {
if(y != from)//这个地方特别关注
dfs(x,y);
}
}
dfs(-1,x);
树的直径
树的直径可以存在多条
树的直径中间的节点为树的中心,如果直径为偶数,中间的两个节点都可以是树的中心,树的中心到其它点的最长路径最短
随机选取一个点,然后以这个点搜索距离最远的点
两次dfs/bfs求直径
优:可记录直径的路径,可计算出直径长度
缺:只适用于无负权边的树
//隐约雷鸣 阴霾天空 但盼风雨来 能留你在此
#include <bits/stdc++.h>
const int maxn = 1e5+10;
#define fsync ios::sync_with_stdio(false);cin.tie(nullptr),cout.tie(nullptr)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
int n;
vector<int> eg[maxn];
int dis[maxn];
int pre[maxn];
void dfs(int x){
for(auto y : eg[x]){
if(y != pre[x]){
pre[y] = x;
dis[y] = dis[x]+1;
dfs(y);
}
}
}
int main(){
cin >> n;
for(int i = 1; i < n; i++){
int u,v;
cin >> u >> v;
eg[u].push_back(v);
eg[v].push_back(u);
}
pre[1] = -1;
dfs(1);
int maxd = 0,idx = 0;
for(int i = 1; i <= n; i++){
if(dis[i] > maxd){
idx = i,
maxd = dis[i];
}
}
memset(dis,0,sizeof(dis));
memset(pre,0,sizeof(pre));
pre[idx] = -1;
dfs(idx);
maxd = 0;
for(int i = 1; i <= n; i++){
maxd = max(maxd,dis[i]);
}
cout << maxd << endl;
return 0;
}
求路径
void dfs_p(int x,int f,int e){
if(tg == true) return ;
for(int i = head[x]; ~i; i = eg[i].nxt){
if(tg == true) return ;
if(eg[i].to == f) continue;
if(eg[i].to == e){
tg = true;
ft[x] = eg[i].to;
return ;
}
ft[x] = eg[i].to;//里面存的就是节点
dfs_p(eg[i].to,x,e);
if(tg == true) return ;
}
}
//起点 0 终点
//均用二次dfs求出了
dfs_p(s,0,e);
树形dp求直径
优:可求有负权边的树,可算出直径长度
缺:无法记录路径
死记硬背代码 用一次dfs求
设
有
void dp(int x){
v[x] = 1;
for(int i = head[x]; ~i; i = eg[i].nxt){
int y = eg[i].to;
if(vis[y]) continue;
dp(y);
ans = max(ans,d[x]+d[y]+eg[i].w);
d[x] = max(d[x],d[y]+eg[i].w);
}
}
树的重心
对于无根树,删除节点
一棵树可能有
树的重心的性质:
-
当重心为根节点时,它底下每个子树的大小不大于整棵树大小的一半
-
重心到其他所有节点的距离和最小
//隐约雷鸣 阴霾天空 但盼风雨来 能留你在此
#include<cstdio>
#include <cmath>
#include <algorithm>
const int maxn = 5e4+10;
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
int n,cnt;
struct node{
int to,nxt;
}eg[maxn<<1];
int head[maxn<<1];
void init(){
for(int i = 0; i < maxn; i++){
eg[i].nxt = -1,head[i] = -1;
}
cnt = 0;
}
void add(int u,int v){
eg[cnt].nxt = head[u];
eg[cnt].to = v;
head[u] = cnt++;
}
int d[maxn],ans[maxn],num,maxnum = 1e9;
void dfs(int u,int fa){
d[u] = 1;
int tmp = 0;
for(int i = head[u]; ~i; i = eg[i].nxt){
int v = eg[i].to;
if(v == fa) continue;
dfs(v,u);
d[u]+=d[v];
tmp = max(d[v],tmp);
}
tmp = max(tmp,n-d[u]);
//cout << u << " " << d[u] << " " << n-d[u] << " "<< tmp << endl;
if(tmp < maxnum){
num = 0;
maxnum = tmp;
ans[++num] = u;
}else if(tmp == maxnum) ans[++num] = u;
return ;
}
int main(){
scanf("%d",&n);
init();
for(int i = 1; i < n; i++){
int u,v;
scanf("%d %d",&u,&v);
add(u,v),add(v,u);
}
dfs(1,0);
sort(ans+1,ans+1+num);
for(int i = 1; i <= num; i++){
printf("%d ",ans[i]);
}
return 0;
}
LCA(最近公共祖先)
对于两个节点
倍增法求LCA
时间复杂度
Fst,
把
x 和y 提到相同的深度,倍增跳,从大到小跳dfs$ 时预处理 $fa[x][i] = fa[a[x][i-1]][i-1] 一共跳了
2^{i-1} + 2^{i-1} = 2^i 步特别的,
fa[x][0] 是x 的父节点
Snd.
当 $fa[x][i] = fa[y][i]$ 时,这是一个公共祖先,它的深度小于等于$LCA(x,y)$,这说明跳过头了,退回去换一个小的 $i-1$ 重新跳 当 $fa[x][i] \ne fa[y][i]$ 时,更新 $x = fa[x][i]$ ,$y = fa[y][i] 最后肯定跳到离LCA差一层深度的地方,所以返回
fa[x][0]
void dfs(int u,int f){
deep[u] = deep[f]+1;
fa[u][0] = f;
for(int i = 1; (1<<i) <= deep[u]; i++){
fa[u][i] = fa[fa[u][i-1]][i-1];//※重要递推式子
}
for(int i = head[u]; ~i; i = eg[i].nxt){
if(eg[i].to != f){
dfs(eg[i].to,u);
}
}
}
int LCA(int x,int y){
//让x位于更低层,深度更大的位置
if(deep[x] < deep[y]) swap(x,y);
//1.把x和y提到相同的深度
for(int i = 19; i >= 0; i--){//2^19 > 5e5+5;
if(deep[x]-(1<<i) >= deep[y])
x = fa[x][i];
}
if(x == y) return x;
//2.把x和y同步往上跳,找到LCA
for(int i = 19; i >= 0; i--){
if(fa[x][i] != fa[y][i]){
x = fa[x][i],y = fa[y][i];
}
}
return fa[x][0];
}
Tarjan求LCA
学不动了,不学了(bushi)
时间复杂度很好,并且无法优化
LCA应用
- 求树上两点间的最短距离
- 树上差分
void dfs(int u,int pre){
for(int i = head[u]; ~i; i = eg[i].nxt){
int v = eg[i].to;
if(v == pre) continue;
dfs(v,u);
dist[u] += dist[v];
}
ans = max(ans,dist[u]);
}
for(int i = 1; i <= k; i++){
int s,t;
cin >> s >> t;
int l = LCA(s,t);
dist[s]++,dist[fa[l][0]]--,dist[t]++,dist[l]--;
}
dfs(1,0);