在讲树链剖分之前,让我们看看求LCA可以用什么算法:
Tarjian 倍增 (暂时只想到这么一些)
倍增求LCA理论复杂度O(n*m*logn)
对于Tarjian求LCA的理论复杂度是O(n*m)按理说复杂度比倍增优秀,但不知道为何在洛谷上交要加快读才能勉强卡过
个人认为Tarjian写LCA很恼火,就是dfs,感觉很蠢而且还要离线操作,弄得人不舒服
倍增是最好写的,但是常数不小
最优秀的就是树链剖分了,理论复杂度最大为O(logn*m),带个2的常数
既然如此优越,那就学习一下啊
【如何用树链剖分求LCA】
1、首先判断当前两个节点是不是拥有同样的链顶,如果有同样的链顶,那么深度小的节点一定LCA(想一想为什么)
2、如果不是同链顶,我们就对链顶深度大的点进行操作,让其跳到链顶的父亲节点(之所以要跳的父节点是为了防止当前节点的链顶就是自己)
3、然后回到1进行判断,直到同链顶
是不是感觉还是很好理解的,如果还是不理解,那就拿下面的图手推一下
1 int lca(int a,int b) 2 { 3 while(top[a]!=top[b]) 4 { 5 if(dep[top[a]]>dep[top[b]]) a=fa[top[a]]; 6 else b=fa[top[b]]; 7 } 8 if(dep[a]<dep[b]) return a; 9 else return b; 10 }
证明其复杂度:
我们单次操作只需要对两条链进行操作,对吧,这就是常数2
我们每条链跳的次数不会超过logn次,很显然(树的优越性质),如果就是一条长链的话,那么很舒服一次性就跳到了链顶并不需要logn的复杂度
【代码实现】
1 #include<cstdio> 2 #include<vector> 3 using namespace std; 4 const int maxn=500000; 5 int top[maxn+5],fa[maxn+5],son[maxn+5],siz[maxn+5],dep[maxn+5],head[maxn+5]; 6 struct sd{ 7 int to,next; 8 }edge[2*maxn+10]; 9 int cnt; 10 void add(int a,int b) 11 { 12 edge[++cnt].to=b; 13 edge[cnt].next=head[a]; 14 head[a]=cnt; 15 } 16 int dfs(int now,int ff,int deep) 17 { 18 fa[now]=ff,dep[now]=deep,siz[now]=1; 19 int ms=-1,maxx=-1; 20 for(int i=head[now];i!=0;i=edge[i].next) 21 { 22 if(edge[i].to!=ff) 23 { 24 int gg=dfs(edge[i].to,now,deep+1); 25 if(maxx<gg) 26 { maxx=gg,ms=edge[i].to; } 27 siz[now]+=gg; 28 } 29 } 30 if(ms==-1) son[now]=now; 31 else son[now]=ms; 32 return siz[now]; 33 } 34 void dfs2(int now,int tt) 35 { 36 top[now]=tt; 37 if(son[now]==now) return; 38 dfs2(son[now],tt); 39 for(int i=head[now];i!=0;i=edge[i].next) 40 { 41 if(edge[i].to!=fa[now]&&edge[i].to!=son[now]) 42 dfs2(edge[i].to,edge[i].to); 43 } 44 } 45 int lca(int a,int b) 46 { 47 while(top[a]!=top[b]) 48 { 49 if(dep[top[a]]>dep[top[b]]) a=fa[top[a]]; 50 else b=fa[top[b]]; 51 } 52 if(dep[a]<dep[b]) return a; 53 else return b; 54 } 55 int main() 56 { 57 int n,m,root; 58 scanf("%d%d%d",&n,&m,&root); 59 for(int i=1;i<n;i++) 60 { 61 int a,b; 62 scanf("%d%d",&a,&b); 63 add(a,b);add(b,a); 64 } 65 siz[root]=dfs(root,0,1); 66 dfs2(root,root); 67 for(int i=1;i<=m;i++) 68 { 69 int a,b; 70 scanf("%d%d",&a,&b); 71 printf("%d\n",lca(a,b)); 72 } 73 return 0; 74 }
最后温馨提示一下,对于存边的话,个人建议还是要用链式前向星,会比vector快不少的,不然洛谷上过不了