树链剖分求LCA

这里先推荐两道练习的裸题

 

首先是求点

 

【codevs4605】 LCA

就是求两个点的公共祖先,每次询问xor上上一个询问的答案。

 

先是两遍DFS:

dfs1:把dep、siz、son求出来

dfs2:求出top和w

 

siz[v]表示以v为根的子树的节点数

dep[v]表示v的深度(根深度为1)

top[v]表示v所在的链的顶端节点

fa[v]表示v的父亲

son[v]表示与v在同一重链上的v的儿子节点

w[v]结点编号

 

 1 int lca(int x,int y)
 2 {
 3   while (top[x]!=top[y])
 4   {
 5     int a=fa[top[x]];
 6     int b=fa[top[y]];
 7     if (dep[a]<dep[b])
 8       swap(a,b),swap(x,y);
 9     x=fa[top[x]];
10   }
11   return dep[x]<dep[y] ? x : y;
12 }

 

下面来分析这个算法。

 

1:如果top[a]==top[b],说明a和b在同一条重链上,显然它们中深度较小的点即为它们的最近公共祖先。

2:如果top[a]!=top[b],(说明a,b在不同的重链上)且a的深度较大,则此时a,b的LCA不可能在a所在的重链上。

因为如果a,b的LCA在a所在重链上,那么top[a]显然也为a,b的公共祖先,则若dep[up[a]]]>dep[b],则显然不可能,若dep[up[a]]]<=dep[b],则设dep[up[a]]]为d,因为d>=dep[b],所以我沿着b向上搜索,在深度d处也可以找到一个点C为b的祖先,又因为a,b不在同一条重链上,所以top[a]!=C,这就意味着在同一深度,b有两个不同的祖先,这是不可能的(因为是一棵树),所以,LCA不可能在a所在的重链上。所以我们可以将a上升到up[a]的父节点,这时a,b的LCA没有变化。

3:若果top[a]!=top[b],且b的深度较大,同理我们可将b上升到up[b]的父节点。

4: a,b不停地上升,所以一定可以找到a,b的LCA。

因为我们知道,在树中任意一点到根的路径上,重链数不会超过(logn),所以我们可以在O(logn)的时间复杂度内,将a,b的LCA求出来。

(证明来自:http://www.xuebuyuan.com/552070.html)

 1 #include<algorithm>
 2 #include<iostream>
 3 #include<cstdlib>
 4 #include<cstring>
 5 #include<cstdio>
 6 #include<cmath>
 7 using namespace std;
 8 
 9 #define N 100010
10 
11 struct edge
12 {
13     int to,next;
14 }e[N<<1];
15 int head[N<<1];
16 int cnt;
17 
18 int fa[N],dep[N],son[N],top[N],siz[N],pos[N];
19 
20 int n,m;
21 
22 int x,y;
23 int ans;
24 
25 int root;
26 
27 void link(int x,int y)
28 {
29     e[++cnt]=(edge){x,head[y]};
30     head[y]=cnt;
31 }
32 
33 void dfs(int x,int d)
34 {
35     dep[x]=d;
36     siz[x]++;
37     for (int i=head[x];i;i=e[i].next)
38     {
39         dfs(e[i].to,d+1);
40         siz[x]+=siz[e[i].to];
41         if (siz[e[i].to]>siz[son[x]])
42             son[x]=e[i].to;
43     }
44 }
45 
46 void dfs2(int x,int d)
47 {
48     pos[x]++;
49     top[x]=d;
50     if (son[x])
51         dfs2(son[x],d);
52     for (int i=head[x];i;i=e[i].next)
53         if (e[i].to!=son[x])
54             dfs2(e[i].to,e[i].to);
55 }
56 
57 int lca(int x,int y)
58 {
59     while (top[x]!=top[y])
60     {
61         int a=fa[top[x]];
62         int b=fa[top[y]];
63         if (dep[a]<dep[b])
64             swap(a,b),swap(x,y);
65         x=fa[top[x]];
66     }
67     return dep[x]<dep[y] ? x : y;
68 }
69 
70 int main()
71 {
72     scanf("%d",&n);
73     for (int i=1;i<=n;i++)
74     {
75         scanf("%d",&fa[i]);
76         if (!fa[i])
77         {
78             root=i;
79             continue;
80         }
81         link(i,fa[i]);
82     }
83     dfs(root,1);
84     cnt=0;
85     top[root]=root;
86     dfs2(root,root);
87     scanf("%d",&m);
88     while (m--)
89     {
90         scanf("%d%d",&x,&y);
91         x^=ans;
92         y^=ans;
93         ans=lca(x,y);
94         printf("%d\n",ans);
95     }
96     return 0;
97 }
codevs4605

 

然后是求值

【codevs2370】 小机房的树

大意:求树上两点距离

 

我写了树链剖分

 1 #include<algorithm>
 2 #include<iostream>
 3 #include<cstdlib>
 4 #include<cstring>
 5 #include<cstdio>
 6 #include<cmath>
 7 using namespace std;
 8 
 9 #define N 100010
10 
11 struct edge
12 {
13 int to,next;
14 }e[N<<1];
15 int head[N<<1];
16 int cnt;
17 
18 int fa[N],dep[N],son[N],top[N],siz[N],pos[N];
19 
20 int n,m;
21 
22 int x,y;
23 int ans;
24 
25 int root;
26 
27 void link(int x,int y)
28 {
29   e[++cnt]=(edge){x,head[y]};
30   head[y]=cnt;
31 }
32 
33 void dfs(int x,int d)
34 {
35   dep[x]=d;
36   siz[x]++;
37   for (int i=head[x];i;i=e[i].next)
38   {
39     dfs(e[i].to,d+1);
40     siz[x]+=siz[e[i].to];
41     if (siz[e[i].to]>siz[son[x]])
42       son[x]=e[i].to;
43   }
44 }
45 
46 void dfs2(int x,int d)
47 {
48   pos[x]++;
49   top[x]=d;
50   if (son[x])
51     dfs2(son[x],d);
52   for (int i=head[x];i;i=e[i].next)
53     if (e[i].to!=son[x])
54       dfs2(e[i].to,e[i].to);
55 }
56 
57 int lca(int x,int y)
58 {
59   while (top[x]!=top[y])
60   {
61     int a=fa[top[x]];
62     int b=fa[top[y]];
63     if (dep[a]<dep[b])
64       swap(a,b),swap(x,y);
65     x=fa[top[x]];
66   }
67   return dep[x]<dep[y] ? x : y;
68 }
69 
70 int main()
71 { 
72   scanf("%d",&n);
73   for (int i=1;i<=n;i++)
74   {
75     scanf("%d",&fa[i]);
76     if (!fa[i])
77     {
78       root=i;
79       continue;
80     }
81     link(i,fa[i]);
82   }
83   dfs(root,1);
84   cnt=0;
85   top[root]=root;
86   dfs2(root,root);
87   scanf("%d",&m);
88   while (m--)
89   {
90     scanf("%d%d",&x,&y);
91     x^=ans;
92     y^=ans;
93     ans=lca(x,y);
94     printf("%d\n",ans);
95   }
96   return 0;
97 }
codevs2370

复杂度O(qlogn),还是比较快的。
posted @ 2016-04-18 21:41  Yangjiyuan  阅读(762)  评论(2编辑  收藏  举报