树上问题

树上问题

树的DFS序

dfs序就是指一棵树被dfs时所经过的节点的顺序

知道子树中节点的 DFS 序是连续的,所以我们可以用一些区间询问数据结构得到关于子树的信息
例题见暑假专题训练,里面摘记的两道题蛮有意思的
下为求一个普通的dfs序的代码

void dfs(int x,int fa){
	l[x]=++xu;//得l
	for(int i=head[x];i;i=p[i].next){//链式前向星
		if(p[i].to==fa) continue;
		dfs(p[i].to,x);
	}
	r[x]=xu;//得r
	return ;
}

例题

  • cf 907 div.2 F. A Growing Tree // Qiansui_code
    利用dfs序找到子树的信息。对于两种操作,我们考虑离线操作。对于操作一:遇到新增节点时,再dfs序生成数组中置其子树初值为 0,这个置 0 很重要!;对于操作二:差分 \(O(1)\) 修改子树权值即可。对于操作一的操作是为了消除该子树上的点还没有放进树中时已经修改的影响

树的欧拉序

就是从根结点出发,按dfs的顺序再绕回原点所经过所有点的顺序

进dfs时加点,出dfs时加点即可得到欧拉序

相关资料

树的dfs序和欧拉序


树的重心

对于树上的每一个点,计算其所有子树中最大的子树节点数,这个值最小的点就是这棵树的重心
性质:
树的重心如果不唯一,则至多有两个,且这两个重心相邻。
以树的重心为根时,所有子树的大小都不超过整棵树大小的一半。
树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么到它们的距离和一样。
把两棵树通过一条边相连得到一棵新的树,那么新的树的重心在连接原来两棵树的重心的路径上。
在一棵树上添加或删除一个叶子,那么它的重心最多只移动一条边的距离。

求解树的重心,利用dfs求解树的子树节点数的同时维护最大子树的最小值即可

例题

模板题

poj Godfather
这题输入用scanf和printf,关流cincout TLE

//>>>Qiansui
#include<map>
#include<set>
#include<list>
#include<stack>
#include<cmath>
#include<queue>
#include<deque>
#include<cstdio>
#include<string>
#include<vector>
#include<utility>
#include<iomanip>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<functional>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x,y,sizeof(x))
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
//#define int long long

inline ll read()
{
	ll x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-48;ch=getchar();}
	return x*f;
}

using namespace std;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<ull,ull> pull;
typedef pair<double,double> pdd;
/*

*/
const int maxm=5e4+5,inf=0x3f3f3f3f,mod=998244353;
int n,cnt=1,head[maxm],sz[maxm],cans=inf,ans[maxm],pos=0;

struct node{
	int to,next;
}p[maxm<<1];

void add_edge(int a,int b){
	p[cnt].to=b;	p[cnt].next=head[a];
	head[a]=cnt++;	return ;
}

void dfs(int x,int fa){
	sz[x]=1;
	int m=0;
	for(int i=head[x];i;i=p[i].next){
		int v=p[i].to;
		if(v==fa) continue;
		dfs(v,x);
		m=max(m,sz[v]);
		sz[x]+=sz[v];
	}
	m=max(m,n-sz[x]);
	if(m<cans){
		cans=m;
		pos=0;
		ans[pos++]=x;
	}else if(m==cans){
		ans[pos++]=x;
	}
	return ;
}

void solve(){
	scanf("%d",&n);
	int a,b;
	for(int i=1;i<n;++i){
		scanf("%d %d",&a,&b);
		add_edge(a,b);
		add_edge(b,a);
	}
	dfs(1,0);
	sort(ans,ans+pos);
	for(int i=0;i<pos;++i){
		printf("%d ",ans[i]);
	}
	return ;
}

signed main(){
	// ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
	int _=1;
//	cin>>_;
	while(_--){
		solve();
	}
	return 0;
}

相关资料

  1. https://oi-wiki.org/graph/tree-centroid/

树的直径

树上任意两节点之间最长的简单路径即为树的直径

两种求法:
一、两边DFS
首先从任意节点 x 开始进行第一次 DFS,到达距离其最远的节点,记为 y,然后再从 y 开始做第二次 DFS,到达距离 y 最远的节点,记为 z,则 y 和 z 即为树的一条直径的两个端点。

优点:能得到完整的路径
缺点:不能用于有负权边的树

二、树形DP求解
我们记录当 1 为树的根时,每个节点作为子树的根向下,所能延伸的最长路径长度 \(d_1\) 与次长路径(与最长路径无公共边)长度 \(d_2\),那么直径就是对于每一个点,该点 \(d_1 + d_2\) 能取到的值中的最大值。

优点:允许树上有负权边
缺点:只能求直径的长度,无法得到完整路径

例题

综合运用

转化问题为求标记点间的最长链长度l,(l + 1) / 2 即为答案
Qiansui_code


模板题

//>>>Qiansui
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define mem(x,y) memset(x, y, sizeof(x))
#define debug(x) cout << #x << " = " << x << '\n'
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << '\n'
//#define int long long

using namespace std;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ull, ull> pull;
typedef pair<double, double> pdd;
/*

*/
const int N = 1e4 + 5, inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f, mod = 998244353;
int n, d[N], x;
vector<int> e[N];

void dfs(int u, int fa){
	for(auto v : e[u]){
		if(v == fa) continue;
		d[v] = d[u] + 1;
		dfs(v, u);
		if(d[v] > d[x]) x = v;
	}
	return ;
}

void solve(){
	cin >> n;
	for(int i = 1; i < n; ++ i){
		int u, v;
		cin >> u >> v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs(1, 0);
	d[x] = 0;
	dfs(x, 0);
	cout << d[x] << '\n';
	return ;
}

signed main(){
	// freopen("in.txt", "r", stdin);
	// freopen("out.txt", "w", stdout);
	ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
	int _ = 1;
	// cin >> _;
	while(_ --){
		solve();
	}
	return 0;
}
int n;
ll d1[N], d2[N], d;
vector<pii> e[N];

void dfs(int u, int fa){
	d1[u] = d2[u] = 0;
	for(auto & [v, w] : e[u]){
		if(v == fa) continue;
		dfs(v, u);
		ll t = d1[v] + w;
		if(t > d1[u]) d2[u] = d1[u], d1[u] = t;
		else if(t > d2[u]) d2[u] = t;
	}
	d = max(d, d1[u] + d2[u]);
	return ;
}

void solve(){
	cin >> n;
	for(int i = 1; i < n; ++ i){
		int u, v, w;
		cin >> u >> v >> w;
		e[u].push_back({v, w});
		e[v].push_back({u, w});
	}
	dfs(1, 0);
	cout << d << '\n';
	return ;
}

相关资料

https://oi-wiki.org/graph/tree-diameter/


LCA最近公共祖先

传送门


树链剖分

传送门


2023ACM暑假训练day 10 树上问题

关于树的dfs序、树的重心、LCA、树链树剖、启发式合并的练习题

posted on 2023-07-08 09:59  Qiansui  阅读(28)  评论(0编辑  收藏  举报