倍增求LCA

P3379 【模板】最近公共祖先(LCA)ios

【題目大意】

給你樹的節點個數\(n\),詢問個數\(m\)和樹根\(s\),
輸入\(n,m,s\);
輸入\(n\)\(x,y.\)表示\(x,y\)結點之間有一條邊
輸入\(m\)\(a,b.\)表示求\(a,b\)的最近公共祖先git

【思路】

下面的解釋均以這個圖爲例

今天用這道題來講一下倍增,先預處理出每一個節點的深度,和lg數組,就是\(log_2{i}\)的值.所謂倍增,就是按\(2\)的倍數來增大,也就是跳\(1,2,4,8,16,32……\)不過在這咱們不是按從小到大跳,而是從大向小跳,即按\(……32,16,8,4,2,11\)來跳,若是大的跳不過去,再把它調小。這是由於從小開始跳,可能會出現「悔棋」的現象。拿\(5\)爲例,從小向大跳,\(5≠1+2+4\),因此咱們還要回溯一步,而後才能得出\(5=1+4\);而從大向小跳,直接能夠得出\(5=4+1\)。這也能夠拿二進制爲例,\(5(101)\),從高位向低位填很簡單,若是填了這位以後比原數大了,那我就不填,這個過程是很好操做的。
仍是以\(17\)\(18\)爲例(此例只演示倍增,並非倍增LCA算法的真正路徑)
\(17->3\)
\(18->5->3\)
能夠看出向上跳的次數大大減少。這個算法的時間複雜度爲\(O(nlogn)\),已經能夠知足大部分的需求。
想要實現這個算法,首先咱們要記錄各個點的深度和他們\(2^{i}\)級的的祖先,用數組\(depth\)表示每一個節點的深度,\(fa[i][j]\)表示節點\(i\)\(2^j\)級祖先。 代碼以下:算法

void dfs(int f,int fath)//f表示當前節點,fath表示他的父親
{
    depth[f]=depth[fath]+1;
    fa[f][0]=fath;
    for(int i=1;(1<<i)<=depth[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=e[i].nex)
      if(e[i].t!=fath)
        dfs(e[i].t,f);
}

預處理完畢後,咱們就能夠去找它的\(LCA\)了,爲了讓它跑得快一些,咱們能夠加一個常數優化數組

for(int i=1;i<=n;i++) //預先算出log_2(i)+1的值,用的時候直接調用就能夠了
  lg[i]=lg[i-1]+(1<<lg[i-1]==i);  //看不懂的能夠手推一下

接下來就是倍增\(LCA\)了,咱們先把兩個點提到同一高度,再統一開始跳。
但咱們在跳的時候不能直接跳到它們的\(LCA\),由於這可能會誤判,好比\(4\)\(8\),在跳的時候,咱們可能會認爲\(1\)是它們的\(LCA\),但\(1\)只是它們的祖先,它們的\(LCA\)實際上是\(3\)。因此咱們要跳到它們\(LCA\)的下面一層,好比\(4\)\(8\),咱們就跳到\(4\)\(5\),而後輸出它們的父節點,這樣就不會誤判了。優化

int lca(int x,int y)
{
    if(depth[x]<depth[y])//保證x比y深.便於一會調到同一深度
      swap(x,y);
    while(depth[x]>depth[y])//調到同一深度.
      x=fa[x][lg[depth[x]-depth[y]]-1];
    if(x==y)//若是到了同一個點了,那就到了lca了.
      return x;
    for(int k=lg[depth[x]]-1;k>=0;k--)//不斷往上調.
      if(fa[x][k]!=fa[y][k])//若是兩個父親不同. 
        x=fa[x][k],y=fa[y][k];//繼續往上跳
    return fa[x][0];//返回
}

完整的求\(17\)\(18\)的LCA的路徑:
\(17->10->7->3\)
\(18->16->8->5->3\)
解釋:首先,\(18\)要跳到和\(17\)深度相同,而後\(18\)\(17\)一塊兒向上跳,一直跳到\(LCA\)的下一層(1\(7\)\(7\)\(18\)\(5\)),此時\(LCA\)就是它們的父親
這個題就是這樣.\(so\) \(easy\).
完整代碼spa

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<string>
#include<algorithm>
#include<iomanip>
#include<cstdlib>
#include<queue>
#include<map>
#include<set>
#include<stack>
#include<vector>
#define ll long long
using namespace std;
inline int read()
{
   int s=0,w=1;
   char ch=getchar();
   while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
   while(isdigit(ch)) s=s*10+ch-'0',ch=getchar();
   return s*w;
}
struct yyy
{
    int t,nex;
}e[2*500001];
int depth[500001],fa[500001][22],lg[500001],head[500001];
int tot;
void add(int x,int y)
{
    e[++tot].t=y; 
    e[tot].nex=head[x];
    head[x]=tot;
}
void dfs(int f,int fath)
{
    depth[f]=depth[fath]+1;
    fa[f][0]=fath;
    for(int i=1;(1<<i)<=depth[f];i++)
      fa[f][i]=fa[fa[f][i-1]][i-1];
    for(int i=head[f];i;i=e[i].nex)
      if(e[i].t!=fath)
        dfs(e[i].t,f);
}
int lca(int x,int y)
{
    if(depth[x]<depth[y])
      swap(x,y);
    while(depth[x]>depth[y])
      x=fa[x][lg[depth[x]-depth[y]]-1];
    if(x==y)
      return x;
    for(int k=lg[depth[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 n,m,s;
int main()
{
    n=read(),m=read(),s=read();
    for(int i=1,x,y;i<=n-1;i++)
        x=read(),y=read(),add(x,y),add(y,x);
    dfs(s,0);
    for(int i=1;i<=n;i++)
      lg[i]=lg[i-1]+(1<<lg[i-1]==i);
    for(int i=1,x,y;i<=m;i++)
        x=read(),y=read(),printf("%d\n",lca(x,y));
    return 0;
}

倍增也就是這個思想了吧.3d

相關文章
相關標籤/搜索