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


    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

  • 相关阅读:
    Rancher安装
    JDK8日期时间对象
    String经典面试题
    String
    单例模式
    多线程
    接口
    代码块
    内存吞金兽(Elasticsearch)的那些事儿 -- 常见问题痛点及解决方案
    内存吞金兽(Elasticsearch)的那些事儿 -- 写入&检索原理
  • 原文地址:https://www.cnblogs.com/pjxpjx/p/12431613.html
Copyright © 2020-2023  润新知