最近公共祖先就字面意思,兩個節點一起往上跳,找到的最近的公共點
找到u和v第一個不同祖先不同的位置,然後這個位置向上走一步就是最近公共的祖先
但是想找到u,v第一個不同祖先的位置,就要保證u,v在同一深度(才能一起往上移動)
所以這個過程分為三部分,
1. 預處理找到每個節點深度
2. 把較深的一點移動到較淺一點的高度
3. 兩個一起往上移動直到他們的父親相同
預處理找深度
這裡找深度可以用一個$deep$陣列存下,用$bfs$找到所有深度,順便可以把父節點也記錄了
void bfs( int s ){ queue <int> fat,step;//佇列存父節點以及深度 fa[s] = s;deep[s] = 1;//根節點的父親還是自己,深度為1 fat.push(s);step.push(1);//放入根節點 int nf,ns;//隊頭的父節點以及深度 vis[s] = 1;//根節點已經遍歷 while( !fat.empty() ){ nf = fat.front(); ns = step.front(); //取隊頭 fat.pop();step.pop(); for( int i = head[nf];i;i = e[i].next ){ //所有能到的邊都算上(因為不知道邊連線的兩個點誰是父節點) int to = e[i].to; if( vis[to] ) continue; //如果目的點已經去過,con掉 fa[to] = nf; deep[to] = ns+1; //記錄對應的數值 vis[to] = 1; st(to); //對該點初始化,一會再說 fat.push(to);step.push(deep[to]); } } }
找公共祖先
在兩點跳到同樣深度後,有兩種做法
(一)一步一步暴力跳
(二)倍增
當然用倍增啦~
那我們就預處理一下$a[i][j]$,記錄每個節點$i$往上跳$2^j$步後,跳到的祖先是誰
因為$i$移動$2^j$次就相當於從i移動$2^{j-1}$次後再移動$2^{j-1}$次 找到狀態轉移方程 $father [ i ] [ j ] = father [ father [ i ] [ j -1] ] [ j-1 ] ;$
然後用$dp$做一個預處理(在$bfs$時查到這個點就處理這個點)
void st( int p ){ a[p][0] = fa[p];//往上跳2^0=1步即找到自己的父節點 for( int i = 1;i <= 20;i++ ) //修改所有能跳的步數 a[p][i] = a[ a[p][i-1] ][i-1]; }
之後就開始跳就完了
int LCA( int x,int y ){ if( deep[x] < deep[y] ) swap( x,y ); //預設x更深 int h = deep[x] - deep[y];//取出高度差 for( int i = 0;i <= 20;i++ ) //保證每個都能測到 if( h & (1<<i) ) //二進位制跳高度 x = a[x][i]; if( x == y ) return y; //如果y是x的祖先,返回y就行 for( int i = 20;i >= 0;i-- ){ //找到第一個不相同的節點 if( a[x][i] != a[y][i] ){ x = a[x][i]; y = a[y][i]; } } return a[x][0]; //公共祖先就是第一個不相同的點的上一個點 }
程式碼如下:
#include<iostream> #include<cstdio> #include<queue> #define NUM 500010 using namespace std; int n,m,s; int fa[NUM],deep[NUM]; int a[NUM][23]; struct bian{ int next,to; }; bian e[NUM<<1]; int head[NUM]; bool vis[NUM]; int cnt; void add( int x,int y ){ e[++cnt].next = head[x]; e[cnt].to = y; head[x] = cnt; } void st( int p ){ a[p][0] = fa[p]; for( int i = 1;i <= 20;i++ ) a[p][i] = a[ a[p][i-1] ][i-1]; } void bfs( int s ){ queue <int> fat,step; fa[s] = s;deep[s] = 1; for( int i = 0;i <= 20;i++ ) a[s][i] = s; fat.push(s);step.push(1); int nf,ns; vis[s] = 1; while( !fat.empty() ){ nf = fat.front(); ns = step.front(); fat.pop();step.pop(); for( int i = head[nf];i;i = e[i].next ){ int to = e[i].to; if( vis[to] ) continue; fa[to] = nf; deep[to] = ns+1; vis[to] = 1; st(to); fat.push(to);step.push(deep[to]); } } } int LCA( int x,int y ){ if( deep[x] < deep[y] ) swap( x,y ); int h = deep[x] - deep[y]; for( int i = 0;i <= 20;i++ )
if( h & (1<<i) ) x = a[x][i];
if( x == y ) return x; for( int i = 20;i >= 0;i-- ){ if( a[x][i] != a[y][i] ){ x = a[x][i]; y = a[y][i]; } } return a[x][0]; } int main(){ cin >> n >> m >> s; int x,y; for( int i = 1;i <= n-1;i++ ){ cin >> x >> y; add( x,y ); add( y,x ); } bfs( s ); // for( int i = 1;i <= n;i++ ) // for( int i = 1;i <= n;i++ ){ // printf( "\n節點i = %d,父親為%d,深度為%d\n",i,fa[i],deep[i] ); // for( int j = 0;j <= 20;j++ ) // printf( " 往上%d下,為%d\n",j,a[i][j] ); // } for( int i = 1;i <= m;i++ ){ cin >> x >> y; int p = LCA(x,y); // if( p == 0 || p == -1 ) cout << s << endl; // else cout << p << endl;; } return 0; }
Warning:加黃部分要注意,確實要這麼寫
如果寫成如下形式則WA
int cnt = 0; while( h&1 ){ x = a[x][cnt]; cnt++; h >>= 1; }
因為如果$h$的二進位制為$11010$,則$while$會退出迴圈