【模板】最近公共祖先 LCA

一個月之前學的最近公共祖先。一直覺得我理解的最夠深入了,直到碰見真的比較複雜的題以後,才發現本身的漏洞。html

那麼今天就藉助一道模板題來總結一下吧。ios

下面是洛谷模板的題面。優化

 

下面是樣例及解釋。spa

Inputhtm

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

Outputblog

 

4
4
1
4
4

 


那麼接下來就詳細說說LCA是怎麼回事吧。get

  首先是通常的LCA。string

我是盜圖小丸子,對圖做者表示歉意與感謝it

  那麼咱們就先舉個栗子。io

  好比4和16的LCA就是3.而對於9和10的LCA則是7.對於17和18則又是3。

  那麼暴力作法就很顯然了不是嗎?


 

暴力作法

  咱們就以17和18爲例。既然是要找LCA,那麼咱們就讓他們往上去走。

  17>14>10>7>3
  18->16->12->8->5->318>16>12>8>5>3

  第一次遇到的地方就是LCA(17,18)的值了。DFS暴力實現啊?

  若是你以爲會這些就足夠了的話(那您可就是神了啊),TLE歡迎你。大多數題通常是不會很快(-->TLE)的。

  不信咱們就舉個栗子吧(仍是上圖),好比4和18.顯然這樣暴力是不太好的。咱們顯然是但願4能夠等18上去了以後再走。

  那麼就有一種很優秀(玄學)的LCA了。


倍增LCA

  當你看到這裏,這題纔剛剛開始啊。

  那麼首先先說倍增吧(有專門講倍增的文章這裏就簡單說一下啦)。

!!倍增

  倍增就是按照2的n次冪來往上走。可是咱們通常是從大往小跳,當用大的跳過了以後,就用小的再跳回來(好蠢的樣子)。

  仍是舉個栗子吧(仍是17和18)

  17>3
  18>5>3

  這樣明顯就是快了很多啊。複雜度是O(nlogn);對於大多數題來講就足夠了。

回到LCA

  因此對於倍增LCA來講,咱們就須要記錄一下每個節點的冪次方爸爸是誰了啊。

  那麼咱們跑一遍dfs就解決了。(deep是節點的深度,fa是存某數的冪次方爸爸的)

 

void dfs(int f,int fath) {
    deep[f]=deep[fath]+1;
    fa[f][0]=fath;
    for(int i=1;(1<<i)<=deep[f];i++) 
        fa[f][i]=fa[fa[f][i-1]][i-1];//意思是f的2^i祖先等於f的2^(i-1)祖先的2^(i-1)祖先  2^i=2^(i-1)+2^(i-1)
    for(int i=head[f];i;i=edge[i].nex)
    	if(edge[i].t!=fath) 
            dfs(edge[i].t,f);
    return;
}

 

  而後咱們就能夠上LCA了。

  在此以前,我喜歡先加一個常數的優化。(固然你也能夠不加,直接套用log2(x)->x是次方,就應該也能夠啊)

 for(int i=1;i<=n;i++) 
        lg[i]=lg[i-1]+(1<<lg[i-1]==i);//看不懂就本身手推好啦,這可救不了你

  而後就是LCA啦,咱們想把他們都調到一個高度再找,這樣就能夠實現了。

int LCA(int x,int y) {
    if(deep[x]<deep[y]) 
        swap(x,y);
    while(deep[x]>deep[y]) 
        x=fa[x][lg[deep[x]-deep[y]]-1];
    if(x==y) 
        return x;
    for(int k=lg[deep[x]]-1;k>=0;k--)
    	if(fa[x][k]!=fa[y][k]) {
          	x=fa[x][k];
            y=fa[y][k];
        }
    return fa[x][0];
}

  很好,我自覺得講的還不錯,勉強看吧(畢竟語文很差)。下面放完整版。

Code(代碼風格2.1版)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;

const int MA=5e5+1;
struct ss{
    int t,nex;
}edge[2*MA];
int n,m,s,tot;
int fa[MA][22];
int lg[MA],head[MA],deep[MA];

int read() {
    int x=0;
    bool flag=0;
    char ch=getchar();
    if(ch=='-') 
        flag=1;
    while(ch<'0'||ch>'9') 
        ch=getchar();
    while(ch>='0'&&ch<='9') {
    	x*=10;
        x+=ch-'0';
        ch=getchar();
    }
    if(flag) return -x;
    return x;
}//快讀壓行什麼的,我纔不要 傲嬌】

void add(int x,int y) {
    edge[++tot].t=y; 
    edge[tot].nex=head[x];
    head[x]=tot;
}

void dfs(int f,int fath) {
    deep[f]=deep[fath]+1;
    fa[f][0]=fath;
    for(int i=1;(1<<i)<=deep[f];i++) 
        fa[f][i]=fa[fa[f][i-1]][i-1];
    for(int i=head[f];i;i=edge[i].nex)
    	if(edge[i].t!=fath) 
            dfs(edge[i].t,f);
    return;
}

int LCA(int x,int y) {
    if(deep[x]<deep[y]) 
        swap(x,y);
    while(deep[x]>deep[y]) 
        x=fa[x][lg[deep[x]-deep[y]]-1];
    if(x==y) 
        return x;
    for(int k=lg[deep[x]]-1;k>=0;k--)
    	if(fa[x][k]!=fa[y][k]) {
          	x=fa[x][k];
            y=fa[y][k];
        }
    return fa[x][0];
}

int main()
{
    n=read();
    m=read();
    s=read();
    for(int i=1;i<n;i++) {
        int x=read();
        int y=read();
        add(x,y); 
        add(y,x);
    }
    for(int i=1;i<=n;i++) 
        lg[i]=lg[i-1]+(1<<lg[i-1]==i);
    dfs(s,0);
    for(int i=1;i<=m;i++) {
        int a=read();
        int b=read();
        int ans=LCA(a,b); 
        printf("%d\n",ans);
    }
    return 0;
}

 

而後我這題還能用 樹鏈剖分,還有約束RMQ求LCA,以及tarjan求LCA(這些我全都不會)。以後會了的話會回來不上的,有興趣的能夠以後在自學一下啦。

那麼就這樣啦。謝謝

相關文章
相關標籤/搜索