[模板] 基础树上问题

一、树的直径

定义:给定一棵树,树种最远的两个节点之间的距离被称为树的直径
树的直径有两种求法,树形DP和两次BFS,时间复杂度都是O(n)。

树形DP求树的直径

设D[x]表示从节点x出发向下走,能够到达的最远节点的距离。设x的子节点为y,则有:
D[x]=max{D[\(y_i\)])+edge(x,yi)};
设F[x]为经过节点x的最长链的长度。 对于x的任意两个子节点yi,yj,f[x]可以通过四个部分构成:从yi到yj子树中的最远距离,边(x,yi),边(x,yj),从yj到yi子树中的最远距离,即:
F[x]=max{D[yi]+D[yj]+edge(x,yi)+edge(x,yj)};
最后的答案为\(F[1]\),故只需求出\(D[1]\)即可。
这种算法的复杂度是\(n^2\)级别的,考虑优化。
可以发现,枚举第j个子节点边,即和前1~j-1个子节点进行结合,而D[yj]+edge(x,yj)为定值。
则可以用类似于单调队列的思想,用一个变量来记录前1~j-1中最大的D[yi]+edge(x,yi),这样就可以做到线性复杂度。
code:

void dp(int u)
{
	vis[u]=1;
	for(int i=h[u];i;i=e[i].nex)
	{
		int v=e[i].v;
		if(!vis[v])
		{
			dp(v);
			maxd=max(maxd,f[v]+f[u]+e[i].w);
			f[u]=max(f[u],f[v]+e[i].w);
		}
	}
}

两次BFS求树的直径

1.从任意一个节点出发,通过DFS或BFS对树进行一次遍历,求出于出发点距离最远的节点,记为p。
2.从节点p出发,通过BFS或DFS再进行一次遍历,求出与p距离最远的节点,记为q。
从p到q的路径就是树的一条直径。(证明略过)
code:

int bfs(int s)
{
	memset(dis,0x3f,sizeof(dis));
	queue<int> q;
	q.push(s);
	dis[s]=pre[s]=0;
	while(q.size())
	{
		int u=q.front();
		q.pop();
		for(int i=h[u];i;i=e[i].nex)
		{
			int v=e[i].v;
			if(dis[v]==INF)
			{
				dis[v]=dis[u]+e[i].w;
				pre[v]=i;
				q.push(v);
			}
		}
	}
	int d=1;
	for(int i=2;i<=n;i++)
	    if(dis[i]>dis[d]) d=i;
	return d;
}
p=bfs(1);
q=bfs(p);

二、树的重心

设size[x]表示以x为根的子树的大小。
定义:对于一个节点p,如果把它从树中删去,那么原来的一棵树可能会分成若干个子树。设max_part(p)表示在删去p后,生成的子树中最大的子树的大小。则令max_part(p)最小的节点就称为树的重心。
code:

void dfs(int u)
{
	vis[u]=true,size[u]=1;
	int max_part=0;
	for(int i=h[u];i;i=e[i].nex)
	{
		int v=e[i].v;
		if(vis[v]) continue;
		dfs(v);
		size[u]+=size[v];
		max_part=max(max_part,size[v]);
	}
	max_part=max(max_part,n-size[u]);
	if(max_part<ans)
	{
		ans=max_part;
		ansnum=u;
	}
}

三、最近公共祖先(LCA)

定义:给定一棵有根树,若节点z满足z既是节点x的祖先,又是节点y的祖先,则称z为(x,y)的公共祖先,在(x,y)的所有公共祖先中,深度最大的一个被称为(x,y)的最近公共祖先,即LCA(x,y)。

树上倍增法

设d[x]表示x的深度。
设F[x][k]表示x的2k次辈祖先,即从x向根节点走2k步到达的节点。若该点不存在,则F[x][k]=0;
F[x][0]就是x的父节点。除此之外,F[x][k]=F[F[x][k-1]][k-1];
算法实现步骤:
1.设d[x]\(\ge\)d[y],若不满足可以交换x,y;
2.把x向上调整到与y同一深度。
3.若x=y,则LCA(x,y)=y;
4.把x,y同时向上跳,保持深度一致且二者不相会。
5.此时,F[x][0]或F[y][0] 即为LCA(x,y);
code:

#include<bits/stdc++.h>
using namespace std;
const int M=5e5+10;
int lg[M],n,m,s,h[M],idx,fa[M][20],depth[M];
struct edge{
        int v,nex;
}e[M<<1];
void add(int u,int v)
{
        e[++idx].v=v;
        e[idx].nex=h[u];
        h[u]=idx;
}
void dfs(int u,int f)
{
        depth[u]=depth[f]+1,fa[u][0]=f;
        for(int i=1;i<=lg[depth[u]];i++) fa[u][i]=fa[fa[u][i-1]][i-1];
        for(int i=h[u];i;i=e[i].nex)
                if(e[i].v!=f) dfs(e[i].v,u);
}
int lca(int x,int y)
{
        if(depth[x]<depth[y]) swap(x,y);
        while(depth[x]>depth[y]) x=fa[x][lg[depth[x]-depth[y]]-1];
        if(x==y) return x;
        for(int i=lg[depth[x]]-1;i>=0;i--)
                if(fa[x][i]!=fa[y][i])
                        x=fa[x][i],y=fa[y][i];
        return fa[x][0];
}
int main()
{
        cin>>n>>m>>s;
        for(int x,y,i=1;i<n;i++)
        {
                cin>>x>>y;
                add(x,y),add(y,x);    
        }
        for(int i=1;i<=n;i++) lg[i]=lg[i-1]+((1<<lg[i-1])==i);
        dfs(s,0);
        while(m--)
        {
                int x,y;
                cin>>x>>y;
                cout<<lca(x,y)<<endl;
        }
        return 0;
}
posted @ 2021-05-28 11:15  曙诚  阅读(83)  评论(0)    收藏  举报