1. 程式人生 > >倍增LCA

倍增LCA

str -c urn 父親 adc main 要求 模板 ++

技術分享圖片
題目描述

如題,給定一棵有根多叉樹,請求出指定兩個點直接最近的公共祖先。
輸入輸出格式
輸入格式:

第一行包含三個正整數N、M、S,分別表示樹的結點個數、詢問的個數和樹根結點的序號。

接下來N-1行每行包含兩個正整數x、y,表示x結點和y結點之間有一條直接連接的邊(數據保證可以構成樹)。

接下來M行每行包含兩個正整數a、b,表示詢問a結點和b結點的最近公共祖先。

輸出格式:

輸出包含M行,每行包含一個正整數,依次為每一個詢問的結果。

輸入輸出樣例
輸入樣例#1: 復制

5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5

輸出樣例#1: 復制

4 4 1 4 4 說明 時空限制:1000ms,128M 數據規模: 對於30%的數據:N<=10,M<=10 對於70%的數據:N<=10000,M<=10000 對於100%的數據:N<=500000,M<=500000 樣例說明: 該樹結構如下: 第一次詢問:2、4的最近公共祖先,故為4。 第二次詢問:3、2的最近公共祖先,故為4。 第三次詢問:3、5的最近公共祖先,故為1。 第四次詢問:1、2的最近公共祖先,故為4。 第五次詢問:4、5的最近公共祖先,故為4。 故輸出依次為4、4144
luogu 3379 lca模板

為什麽要用倍增來求呢?

因為快啊,不用一個一個跳,直接以2的冪次方跳。

那我們講解一下大致過程:

首先我們需要每個點的深度和它往上2的冪次方的節點是哪個;

這個可以用dfs和dp搞出來;

dp【i】【j】表示第i個節點往上跳2的j次方後的節點是哪個;

那麽有轉移dp【i】【j】=dp【dp【i】【j-1】】【j-1】;

然後我們求lca

首先我們要使第一個點是深度大的那個,因為這樣好寫

其次我們枚舉深度(2^k),當2^k<=這兩個點之間的深度差的最大值時;

我們讓x向上跳2^k;

這個地方有一個特判,在代碼裏有,懶得再打一遍了;

然後倒序繼續枚舉深度,如果兩個點都跳2的k次方後為同一個點,那就不用跳了,因為是倒敘枚舉,後面肯定要比前面優,如果不是同一個點,就跳;

最後跳完肯定是兩個點都在lca的下面一個點;

那麽只要求這個點的父親返回就是lca;

下面 ↓

技術分享圖片
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
int f[500050][50],ans[5000000],head[5000000],deep[5000000],cnt,root,n,m,tt,flag,yqy;//f[i][j]表示第i個節點向上跳2的j次方的節點 
struct node 
{
    int to,next;
}edge[1000500];
void add(int x,int y)
{
    cnt++;
    edge[cnt].to=y;
    edge[cnt].next=head[x];
    head[x]=cnt;
}
void dfs(int x,int father){
    deep[x]=deep[father]+1;
    f[x][0]=father;//向上一個節點就是該點的父親; 
    for(int i=1;(1<<i)<=deep[x];i++)
        f[x][i]=f[f[x][i-1]][i-1];//x向上跳2^i可以由x先向上跳2^(i-1)再跳2^(i-1)轉移過來;不是2的i-1次方的i-1次方; 
    for(int i=head[x];i;i=edge[i].next){
        int y=edge[i].to;
        if(y!=father)dfs(y,x);
    }
}
int LCA(int x,int y)
{
    if(deep[x]<deep[y])
    {
        int temp=x;
        x=y;
        y=temp;
    }//保證x為深度大的; 
    for(int i=20;i>=0;i--)
        if(deep[x]-deep[y]>=(1<<i))//如果兩個節點的深度差比2的i次方大; 
            x=f[x][i];//x就往上跳2的i次方; 
    if(x==y) //如果跳完正好在同一個節點,那麽就返回這個節點; 
        return x;
    for(int k=20;k>=0;k--)
        if(f[x][k]!=f[y][k])//如果兩個點都跳2的k次方後為同一個點,那就不用跳了,因為是倒敘枚舉,後面肯定要比前面優,如果不是同一個點,就跳; 
            x=f[x][k],y=f[y][k];
    return f[y][0];//跳完之後肯定保留到兩點只跳一步就相同,所以返回父親就行了; 
}
int main()
{
//    ios::sync_with_stdio(0); 
    int x,y,a,b;
    scanf("%d%d%d",&n,&m,&root);
    for(int i=1;i<=n-1;i++)
    {
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    dfs(root,0);
    for(int i=0;i<m;i++)
    {
          scanf("%d%d",&a,&b);
        printf("%d\n",LCA(a,b));
    }
    return 0;
}
標程加解析

可以轉載,請註明來源

倍增LCA