树的直径与最近公共祖先

1.树的直径

1.1 树形DP求树的直径

1.1.2 思路:设d[x]为从结点x出发走以x为根的子树,能够达到最远结点的距离。设x的子节点为y1,y2,y3...yt,edge(x,yi)表示边的权重,那就有:d[x] = max{ d[yi] + edge(x,yi) }(i <= i <=t)

接下来设经过x的最长链的长度为F[x],那么整棵树的直径就是max{F[x]} (1 <= x <= n)。F[x]可以由四个部分构成:x走yi子树的最远距离,x走yj子树的最远距离,x到yi的距离,x到yj的距离。也就是:F[x] = maxn{ d[yi] + d[yj] + edge(x,yi) + edge(x,yj) }

由于我们已经用d[x]保存从结点x出发走向 “以yj为根的子树(j < i)” ,能够达到的最远距离,这个距离就是max{ d[yj] + edge(x,yj) }。所以,我们只要先用d[x] + d[yi] + edge(x,yi)来更新F[x],再用d[yi] + edge(x,yi) 来更新d[x]即可。

1.2 两次BFS求树的直径

1.2.1 思路:通过两次BFS求出树的直径,更容易计算出直径上的具体结点。做法包括两步:

  1. 从任意一个节点出发,通过BFS或DFS对树进行一次遍历,求出与出发点距离最远的节点记为p
  2. 从节点p出发,通过BFS或DFS再进行一次遍历,求出与p距离最远的节点,记为q

那么p到q的路径就是树的一条直径。因为p一定是直径的一端,而同理q也是树的另一端,故算法成立。

1.2.2 代码示例:

#include<iostream>
#include<cstdio>
#include<queue>
#include<vector>
#include<cstring>
using namespace std;
const int maxn = 100005;
vector<int> G[maxn];//用来存放有向边 
int d[maxn];//存放从起始点到该节点的距离 
int p[maxn];//父节点 
bool vis[maxn];//是否已访问 
int BFS(int s){
	memset(vis,false,sizeof vis);
	memset(d,0,sizeof d);
	memset(p,0,sizeof p);
	int ans;//当前最大距离时的结点 
	int maxlen = -1;//最大距离 
	queue<int> q;
	q.push(s);
	vis[s] = true;
	while(!q.empty()){
		int x = q.front();
		q.pop();
		for(int i = 0;i < G[x].size();i++)	if(!vis[G[x][i]]){
			d[G[x][i]] = d[x]+1;
			p[G[x][i]] = x;
			q.push(G[x][i]);
			vis[G[x][i]] = true;
		}
		if(d[x] > maxlen){
			ans = x;
			maxlen = d[x];
		}
	}
	return ans;//返回最大距离的结点编号 
}

2.最近公共祖先(LCA)

2.1 定义:

给定一颗有根树,若节点z既是节点x的祖先,也是节点y的祖先,则称z是x,y的公共祖先。在x,y所有的公共祖先中,深度最大的一个称为x,y的最近公共祖先,记为LCA(x,y)。

2.2 向上标记法:

2.2.1 思路:

  1. 从x向上走到根节点,并标记所有经过的节点
  2. 从y向上走到根节点,当第一次遇到已标记的节点时,就找到了LCA(x,y)

2.2.2 复杂度分析:最坏为O(n)

2.3 树上倍增法:

2.3.1 模板: 最近公共祖先模板

2.3.2 复杂度分析:每次询问复杂度为O(logn)

2.4 LCA的Tarjan算法:

2.4.1 模板:

2.4.2 复杂度分析:离线算法,需要把m次询问一次性读入,统一输出。时间复杂度O(n+m)

 

《算法竞赛进阶指南——李煜东》P341

posted @ 2018-08-27 11:35  Dr_Lo  阅读(167)  评论(0编辑  收藏  举报