树上两点最近公共祖先LCA的倍增算法 poj1986

【树上两点最近公共祖先】:

LCA(Least Common Ancestor)

对于树图,任意两点的路径是唯一的。当选定了根节点之后,任意选取两点u、v,一定存在一个最近的公共祖先。

多叉树、二叉树都如此。如图:u和v的最近公共祖先就是k


【解题思路】:

朴素的暴力法可以直接搜索,一路走到底,标记寻找,但是复杂度很高,无法应对竞赛题目。

今天学习了离线的倍增算法解决这个问题,时间复杂度O(n*logn)。思想与RMQ算法极为类似。


易知每一个节点的父节点,但是不知道爷爷,爷爷的爷爷。倘若知道,我们就可以根据两节点的深度,直接得出这个公共祖先,但是如果一个节点的每一个祖先都求出来,复杂度又无异于暴力。

此时二分思想横空出世。


我们可以得出每个节点的第2^i 次幂个祖先是谁,例如2^0=1,代表父亲节点。

这样再去找公共节点,就能在logn的时间里完成任务。


大体思想是这样,可还是不清楚怎么操作。

【解题步骤】:

1、需要预处理的操作:

d[]数组记录每个节点到根节点的深度(dfs过程处理)

fa[]数组记录每个节点的父节点  (dfs)

rlen[]数组记录每个节点到根节点的实际距离(dfs)

anc[x][i]数组记录节点x的2^i祖先是谁(dfs)


dfs实现过程可参考下面的例题代码


2、LCA询问,给出两个节点u,v

如果u v的深度不一致,先调节到一致的深度。

当深度一致时,判断一下u==v则return u;

u!=v,则:


从最高的祖先开始找,先看绿线,若是祖先(或不存在),则折半,再判断蓝线。

若不是公共祖先,则令u和v同时把深度提升到这两个不是公共祖先的点,继续找

【例题poj1986】poj1986 http://poj.org/problem?id=1986

Distance Queries
Time Limit: 2000MS
Memory Limit: 30000K
Total Submissions: 14755
Accepted: 5187
Case Time Limit: 1000MS

Description

Farmer John's cows refused to run in his marathon since he chose a path much too long for their leisurely lifestyle. He therefore wants to find a path of a more reasonable length. The input to this problem consists of the same input as in "Navigation Nightmare",followed by a line containing a single integer K, followed by K "distance queries". Each distance query is a line of input containing two integers, giving the numbers of two farms between which FJ is interested in computing distance (measured in the length of the roads along the path between the two farms). Please answer FJ's distance queries as quickly as possible! 

Input

* Lines 1..1+M: Same format as "Navigation Nightmare" 

* Line 2+M: A single integer, K. 1 <= K <= 10,000 

* Lines 3+M..2+M+K: Each line corresponds to a distance query and contains the indices of two farms. 

Output

* Lines 1..K: For each distance query, output on a single line an integer giving the appropriate distance. 

Sample Input

7 6
1 6 13 E
6 3 9 E
3 5 7 S
4 1 3 N
2 4 20 W
4 7 2 S
3
1 6
1 4
2 6

Sample Output

13
3
36

Hint

Farms 2 and 6 are 20+3+13=36 apart. 

题意给的无向图,需要自选一个根节点,不妨选1。然后任意询问两点的距离。

只需求出两点到根节点的距离,减掉两倍的最近公共祖先点到根节点的距离

(题目的方向可忽略)

【代码】:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAX=300000;
struct node{
    int t,len,next;
}e[MAX];
int head[MAX],cnt;
void add(int u,int v,int len)
{
    e[cnt]=node{v,len,head[u]};
    head[u]=cnt++;
}
bool vis[MAX];
int fa[MAX];//father
int rlen[MAX];//length to root!
int d[MAX];//deep
int anc[MAX][21];//LCA
void dfs(int x)//对x点求倍增祖先
{
    anc[x][0]=fa[x];//2^0祖先,即father
    for(int i=1;i<=20;i++)
        anc[x][i]=anc[anc[x][i-1]][i-1];//折半,寻祖辈
    vis[x]=1;
    for(int i=head[x];~i;i=e[i].next)
    {
        int &t=e[i].t;
        if(!vis[t])
        {
            fa[t]=x;
            rlen[t]=rlen[x]+e[i].len;
            d[t]=d[x]+1;
            dfs(t);
        }
    }
}
int LCA(int u,int v)//query shorted anc between u and v;
{
    if(d[u]<d[v])swap(u,v);//let u deep


    for(int i=20;i>=0;i--)
        if(d[anc[u][i]]>=d[v])//u的祖先深于v
            u=anc[u][i];
    if(u==v)return u;
    for(int i=20;i>=0;i--)
        if(anc[u][i]!=anc[v][i])
        {
            u=anc[u][i];
            v=anc[v][i];
        }//最后一定u!=v
    return fa[u];
}
int main()
{
    int n,m,k,x,y,len;char s[4];
    while(~scanf("%d%d",&n,&m))
    {
        cnt=0;
        memset(head,-1,sizeof(head));
        while(m--)
        {
            scanf("%d%d%d%s",&x,&y,&len,s);
            add(x,y,len);
            add(y,x,len);
        }
        memset(vis,0,sizeof(vis));
        rlen[1]=0;
        d[1]=0;
        fa[1]=1;
        dfs(1);//建树+预处理LCA
        scanf("%d",&k);
        while(k--)
        {
            scanf("%d%d",&x,&y);
            printf("%d\n",rlen[x]+rlen[y]-2*rlen[LCA(x,y)]);
        }
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雪的期许

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值