图论:LCA-树上倍增

BZOJ1602

求最近公共祖先有三种常用的方法,在线的有两种,分别是树上倍增算法和转化为RMQ问题

离线的有一种,使用Tarjan算法

这里,我们介绍复杂度优异并且在线的倍增算法,至于后续的两种方法等到之后接触了相关方面的内容之后再进行补充,到时候写一篇总结

我们在计算树上的两点的最短路径时,如果按照图的节奏去求,那势必是会超时的,树有着自己优异的性质,那就是十分适合递归

下面先给出计算树上两点最短路距离的公式:

dis[x]表示根到x的距离
则x,y距离为dis[x]+dis[y]-2*dis[lca(x,y)]

理解起来还是很容易的,下面以BZOJ1602为例来介绍一下实现的方法:

int n,q,cnt;
bool vis[maxn];
int head[maxn],deep[maxn],dis[maxn],fa[maxn][11];
struct data{int to,next,v;}e[2*maxn];

直接用邻接表来存树,然后这里的vis是协同dfs预处理来判重的

deep是每一个节点的深度,dis是这个节点到根节点的距离,fa[x][y]表示x往上倒2^y的祖先是谁,y=0是就是它爹

data不再赘述,邻接表实现不再赘述

void dfs(int x)
{
    vis[x]=1;
    for(int i=1;i<=9;i++)
    {
        if(deep[x]<(1<<i)) break;
        fa[x][i]=fa[fa[x][i-1]][i-1];
    }
    for(int i=head[x];i;i=e[i].next)
    {
        if(vis[e[i].to]) continue;
        deep[e[i].to]=deep[x]+1;
        dis[e[i].to]=dis[x]+e[i].v;
        fa[e[i].to][0]=x;
        dfs(e[i].to);
    }
}

dfs预处理出fa数组,deep数组和vis数组,这是解决很多树问题的一个前置基础

然后就是倍增算法:

int lca(int x,int y)
{
    if(deep[x]<deep[y]) swap(x,y);
    int d=deep[x]-deep[y];
    for(int i=0;i<=9;i++)
        if((1<<i)&d) x=fa[x][i];
    for(int i=9;i>=0;i--)
        if(fa[x][i]!=fa[y][i])
            {x=fa[x][i];y=fa[y][i];}
    if(x==y) return x;
    else return fa[x][0];
}

根据两个节点的的深度,如不同,向上调整深度大的节点,使得两个节点在同一层上,如果正好是祖先结束,否则,将两个节点同时上移,查询最近公共祖先

其实这里所有的实现细节之后都应该自己在纸上画一画熟悉熟悉

最后给出完整的实现:

 1 #include<cstdio>
 2 #include<algorithm>
 3 using namespace std;
 4 const int maxn=1005;
 5 int n,q,cnt;
 6 bool vis[maxn];
 7 int head[maxn],deep[maxn],dis[maxn],fa[maxn][11];
 8 struct data{int to,next,v;}e[2*maxn];
 9 void ins(int u,int v,int w)
10 {e[++cnt].to=v;e[cnt].next=head[u];e[cnt].v=w;head[u]=cnt;}
11 void insert(int u,int v,int w)
12 {ins(u,v,w);ins(v,u,w);}
13 void dfs(int x)
14 {
15     vis[x]=1;
16     for(int i=1;i<=9;i++)
17     {
18         if(deep[x]<(1<<i)) break;
19         fa[x][i]=fa[fa[x][i-1]][i-1];
20     }
21     for(int i=head[x];i;i=e[i].next)
22     {
23         if(vis[e[i].to]) continue;
24         deep[e[i].to]=deep[x]+1;
25         dis[e[i].to]=dis[x]+e[i].v;
26         fa[e[i].to][0]=x;
27         dfs(e[i].to);
28     }
29 }
30 int lca(int x,int y)
31 {
32     if(deep[x]<deep[y]) swap(x,y);
33     int d=deep[x]-deep[y];
34     for(int i=0;i<=9;i++)
35         if((1<<i)&d) x=fa[x][i];
36     for(int i=9;i>=0;i--)
37         if(fa[x][i]!=fa[y][i])
38             {x=fa[x][i];y=fa[y][i];}
39     if(x==y) return x;
40     else return fa[x][0];
41 }
42 int main()
43 {
44     scanf("%d%d",&n,&q);
45     for(int i=1;i<n;i++)
46     {
47         int u,v,w;
48         scanf("%d%d%d",&u,&v,&w);
49         insert(u,v,w);
50     }
51     dfs(1);
52     for(int i=1;i<=q;i++)
53     {
54         int x,y;
55         scanf("%d%d",&x,&y);
56         printf("%d\n",dis[x]+dis[y]-2*dis[lca(x,y)]);
57     }
58     return 0;
59 }

转载于:https://www.cnblogs.com/aininot260/p/9418515.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值