LCA HDU2586 LCA板题 在线倍增

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/DADDY_HONG/article/details/81875019

LCA

最近公共祖先(Lowest Common Ancestor

对于有根树T的两个结点u、v,最近公共祖先LCA(u,v)表示一个结点x,满足x是u、v的祖先且x的深度尽可能大(离uv最近的那个)。

主要是用于处理树上任意点对间的距离。(多次问询很优秀叭)

在线倍增求LCA算法大体思路

(具体代码实现理解原理于注释中给出)

dep[]每个结点距离根节点1的深度,dis[]每个结点距离根节点1的距离,p[i][j]从节点 i 向上跳 2^j 步所到达的祖先。

1.dfs求出每个结点的深度dep[i](1为根结点),到根节点1的距离dis[i],以及父亲结点即p[i][0];

void dfs(int u,int fa,int d)
{
    dep[u]=d;   //求深度
    p[u][0]=fa;
    int sz=e[u].size();
    for(int i=0;i<sz;++i){
        int v=e[u][i].v;
        int w=e[u][i].w;
        if(v!=fa){
            dis[v]=dis[u]+w;
            dfs(v,u,d+1);
        }
    }
}

2.预处理出每个结点向上跳不同 2^j 步所到达的祖先结点,p[u][i]=p[p[u][i-1]][i-1],因为 2^i = 2^(i-1) + 2^(i-1) ;

void init()
{
    for(int i=1;(1<<i)<=n;++i)
        for(int u=1;u<=n;++u)
            p[u][i]=p[p[u][i-1]][i-1];
}

也可以在dfs时同时求出。

void dfs(int u,int fa,int d)
{
    dep[u]=d;   //求深度
    if(u==1){   //以1为祖先
        for(int i=0;i<maxj;++i)
            p[u][i]=1;
    }
    else{   //倍增如二进制压缩
        p[u][0]=fa; //向上跳2^0=1步跳到了父亲
        for(int i=1;i<maxj;++i)
            p[u][i]=p[p[u][i-1]][i-1];
        //u跳2^i步到达的结点 即为 u跳2^(i-1)步到达的结点再跳2^(i-1)到达的结点
        //2^i=2^(i-1)+2^(i-1)
    }
    int sz=e[u].size();
    for(int i=0;i<sz;++i){
        int v=e[u][i].v;
        int w=e[u][i].w;
        if(v!=fa){
            dis[v]=dis[u]+w;
            dfs(v,u,d+1);
        }
    }
}

3.对于每次问询 u,v 之间最短距离:ans = dep[u] + dep[v] - 2*dep [ LCA(u,v) ] 。

求LCA,先使u,v处于同一深度,然后特判返回,否则继续一起往上跳,因为都是倒着遍历的判定都是判定的祖先是否相同,不同时才向上跳,所以不会有跳过了的情况。

int LCA(int u,int v)
{
    if(dep[u]<dep[v]) swap(u,v);
    int d=dep[u]-dep[v];
    for(int j=maxj-1;j>=0;--j){     //向上跳的倍增过程,使u与v到达同一层
        if((1<<j)&d)    //u尽量跳得靠近v但不超过v
            u=p[u][j];      //注意此处,虽然u改变了,d未改变,但下一次跳不会超过v,因为j也减了
    }
    if(u==v){   //特判
        return u;
    }
    for(int i=maxj-1;i>=0;--i){
        if(p[u][i]!=p[v][i]){   //祖先仍不相同,继续往上跳
            u=p[u][i];
            v=p[v][i];
        }
    }
    return p[u][0];     //注意此时u与v并不相同,而是他们得祖先相同
}

板题:HDU2586

How far away ?

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 23476    Accepted Submission(s): 9318

 

Problem Description

There are n houses in the village and some bidirectional roads connecting them. Every day peole always like to ask like this "How far is it if I want to go from house A to house B"? Usually it hard to answer. But luckily int this village the answer is always unique, since the roads are built in the way that there is a unique simple path("simple" means you can't visit a place twice) between every two houses. Yout task is to answer all these curious people.

 

Input

First line is a single integer T(T<=10), indicating the number of test cases.
  For each test case,in the first line there are two numbers n(2<=n<=40000) and m (1<=m<=200),the number of houses and the number of queries. The following n-1 lines each consisting three numbers i,j,k, separated bu a single space, meaning that there is a road connecting house i and house j,with length k(0<k<=40000).The houses are labeled from 1 to n.
  Next m lines each has distinct integers i and j, you areato answer the distance between house i and house j.

 

Output

For each test case,output m lines. Each line represents the answer of the query. Output a bland line after each test case.

 

Sample Input

2

3 2

1 2 10

3 1 15

1 2

2 3

2 2

1 2 100

1 2

2 1

Sample Output

10

25

100

100

 

直接上代码:

#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;

const int maxn=40020;
const int maxm=220;
const int maxj=20;

int n,m;
int dep[maxn];  //求深度
int dis[maxn];  //求距离
int p[maxn][maxj];    //p[i][j]表示结点i向上跳2^j步的结点

struct node{
    int v,w;
    node(int a,int b):v(a),w(b){}
};
vector <node> e[maxn];

//添加边,函数区分过度了
void add_edge(int u,int v,int w)
{
    e[u].push_back(node(v,w));
}

void dfs(int u,int fa,int d)
{
    dep[u]=d;   //求深度
    /*if(u==1){   //以1为祖先
        for(int i=0;i<maxj;++i)
            p[u][i]=1;
    }
    else{   //倍增如二进制压缩
        p[u][0]=fa; //向上跳2^0=1步跳到了父亲
        for(int i=1;i<maxj;++i)
            p[u][i]=p[p[u][i-1]][i-1];
        //u跳2^i步到达的结点 即为 u跳2^(i-1)步到达的结点再跳2^(i-1)到达的结点
        //2^i=2^(i-1)+2^(i-1)
    }*/
    p[u][0]=fa;
    int sz=e[u].size();
    for(int i=0;i<sz;++i){
        int v=e[u][i].v;
        int w=e[u][i].w;
        if(v!=fa){
            dis[v]=dis[u]+w;
            dfs(v,u,d+1);
        }
    }
}

void init()
{
    for(int i=1;(1<<i)<=n;++i)
        for(int u=1;u<=n;++u)
            p[u][i]=p[p[u][i-1]][i-1];
}

int LCA(int u,int v)
{
    if(dep[u]<dep[v]) swap(u,v);
    int d=dep[u]-dep[v];
    for(int j=maxj-1;j>=0;--j){     //向上跳的倍增过程,使u与v到达同一层
        if((1<<j)&d)    //u尽量跳得靠近v但不超过v
            u=p[u][j];      //注意此处,虽然u改变了,d未改变,但下一次跳不会超过v,因为j也减了
    }
    if(u==v){   //特判
        return u;
    }
    for(int i=maxj-1;i>=0;--i){
        if(p[u][i]!=p[v][i]){   //祖先仍不相同,继续往上跳
            u=p[u][i];
            v=p[v][i];
        }
    }
    return p[u][0];     //注意此时u与v并不相同,而是他们得祖先相同
}

int main()
{
    int t;
    int i,j,k;
    int u,v;

    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&m);
        for(int x=1;x<=n;++x)   //初始化
            e[x].clear();
        for(int x=0;x<n-1;++x){
            scanf("%d%d%d",&i,&j,&k);
            add_edge(i,j,k);
            add_edge(j,i,k);
        }
        dis[1]=0;
        dfs(1,0,0);
        init();
        while(m--){
            scanf("%d%d",&u,&v);
            printf("%d\n",dis[u]+dis[v]-2*dis[LCA(u,v)]);
        }
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/DADDY_HONG/article/details/81875019