这里先推荐两道练习的裸题
首先是求点
【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 ",ans); 95 } 96 return 0; 97 }
然后是求值
【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 ",ans); 95 } 96 return 0; 97 }
复杂度O(qlogn),还是比较快的。