学习笔记——树上两点最短距离

By pjx,拿走请附上https://www.cnblogs.com/pjxpjx/p/12431613.html

Update 2020.3.16 修改复杂度写错问题

Update 2020.8.2 添加代码注释

定义

顾名思义,这个东西就是求树上任意两个点 \(u,v\) 的最短距离。

解法(以边权,无向边为例)

1.暴力

随便找一个点为根,把边权挂到儿子节点上,对于每一对 \(u,v\) ,先使用倍增法求出 \(LCA(u,v)\) ,再暴力从 \(u\) 开始向上一步一步跳至 \(LCA(u,v)\) ,每跳一次就加上那条边的权值;再从 \(LCA(u,v)\) 向下一步一步跳至 \(v\), 每跳一次就加上那条边的权值。

复杂度:\(O(n^2)\)

代码太暴力了,就不给了

2.前缀和优化

暴力的算法太暴力了(?),我们可以用前缀和优化一下。
在学数组一维前缀和时,求一段数之和是这样写的:

    cout<<total[v]-total[u-1];//sum表示预处理前缀和的数组

所以,把这个式子拓展到树上,就可以求出一条链上任意两点的前缀和了。
这个只能处理呈一条链的前缀和,对不是链数据难道就无能为力了?
不是!
通过暴力可以看出,求距离可以分成两段,\(u到LCA(u,v)\)\(LCA(u,v)到v\)
所以,我们把求距离砍成两段,最后综合一下就可以了。

先预处理出前缀和数组,然后就按照一维前缀和的样子来做:

    ans+=total[u]-total[LCA(u,v)];
    ans+=total[v]-total[LCA(u,v)];

最后综合并成一个式子:

    cout<<total[u]+total[v]-2*total[LCA(u,v)];

来张图更易理解:

如图,\(LCA(5,3)=4\),所以答案就是\(total[5]+total[3]-2*total[4]\)

这个算法更加优化,复杂度少了许多。

复杂度:\(O(nlogn)\)

code:

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
using namespace std;
const int N=200005;
const int M=25;
int Q,n,m,head[N],fa[N][M],cnt,dep[N],b[N],k,total[N],wei[N];
struct node{
	int u,v,next,sum;
}ed[N]; 
void add_edge(int u,int v,int k) //邻接表存图,不懂的搜索一下
{
	cnt++;
	ed[cnt].u=u;
	ed[cnt].v=v;
	ed[cnt].next=head[u];
	ed[cnt].sum=k;
	head[u]=cnt;
}
void dfs(int xx)
{
	for(int i=1;i<=20;i++)
	{
		fa[xx][i]=fa[fa[xx][i-1]][i-1];//使用倍增思路
	}
	for(int i=head[xx];i!=0;i=ed[i].next)//枚举
	{
		int temp=ed[i].v;
		if(b[temp]==0)
		{
			b[temp]=1;
			dep[temp]=dep[xx]+1;
			total[temp]=ed[i].sum+total[xx];//累加前缀和
			fa[temp][0]=xx;
			dfs(temp);
		}
	}
}
int LCA(int x,int y)
{
	if(dep[x]<dep[y])
	swap(x,y);
	int temp=dep[x]-dep[y];
	for(int i=20;i>=0;i--)
	{
		if(1<<i&temp)
		{
			x=fa[x][i];
		}
	}
	if(x==y)
	return x;
	for(int i=20;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;
	cin>>m;
	for(int i=1;i<n;i++)
	{
		int x,y,k;
		scanf("%d%d%d",&x,&y,&k);
		add_edge(x,y,k);
		add_edge(y,x,k);
	}
	b[1]=1;
	dep[1]=1;//注意开始的初始值
	dfs(1);
	while(m--)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		int lca=LCA(x,y);
		cout<<total[x]+total[y]-total[lca]-total[lca]<<endl; // 根据公式
	}
    return 0;
}

模板题目:

#10134. 「一本通 4.4 练习 1」Dis

#10130. 「一本通 4.4 例 1」点的距离

By pjx

原文地址:https://www.cnblogs.com/pjxpjx/p/12431613.html