树链剖分求LCA

在讲树链剖分之前,让我们看看求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快不少的,不然洛谷上过不了

 

posted @ 2018-04-04 20:38  genius777  阅读(575)  评论(3编辑  收藏  举报