一個月之前學的最近公共祖先。一直覺得我理解的最夠深入了,直到碰見真的比較複雜的題以後,才發現本身的漏洞。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(這些我全都不會)。以後會了的話會回來不上的,有興趣的能夠以後在自學一下啦。
那麼就這樣啦。謝謝