树上前缀和学习应用笔记——树上两点最短距离

上篇
我上次口胡了前缀和模板,这次口胡一下前缀和的应用。

树上两点最短距离

定义

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

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

1.暴力

随便找一个点为根,把边权挂到儿子节点上,对于每一对 \(u,v\) ,先使用倍增法求出 \(LCA(u,v)\) ,再暴力从 \(u\) 开始向上一步一步跳至 \(LCA(u,v)\) ,每跳一次就加上那条边的权值;再从 \(LCA(u,v)\) 向下一步一步跳至 \(v\), 每跳一次就加上那条边的权值。
复杂度(对于每一对\((u,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]\)
这个算法更加优化,复杂度少了许多。
复杂度(对于每一对\((u,v)\)):\(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

扫描二维码关注公众号,回复: 9847066 查看本文章

猜你喜欢

转载自www.cnblogs.com/pjxpjx/p/12431613.html