标签:
这里先推荐两道练习的裸题
首先是求点
【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]结点编号
int lca(int x,int y)
{
while (top[x]!=top[y])
{
int a=fa[top[x]];
int b=fa[top[y]];
if (dep[a]<dep[b])
swap(a,b),swap(x,y);
x=fa[top[x]];
}
return dep[x]<dep[y] ? x : y;
}
下面来分析这个算法。
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)
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define N 100010
struct edge
{
int to,next;
}e[N<<1];
int head[N<<1];
int cnt;
int fa[N],dep[N],son[N],top[N],siz[N],pos[N];
int n,m;
int x,y;
int ans;
int root;
void link(int x,int y)
{
e[++cnt]=(edge){x,head[y]};
head[y]=cnt;
}
void dfs(int x,int d)
{
dep[x]=d;
siz[x]++;
for (int i=head[x];i;i=e[i].next)
{
dfs(e[i].to,d+1);
siz[x]+=siz[e[i].to];
if (siz[e[i].to]>siz[son[x]])
son[x]=e[i].to;
}
}
void dfs2(int x,int d)
{
pos[x]++;
top[x]=d;
if (son[x])
dfs2(son[x],d);
for (int i=head[x];i;i=e[i].next)
if (e[i].to!=son[x])
dfs2(e[i].to,e[i].to);
}
int lca(int x,int y)
{
while (top[x]!=top[y])
{
int a=fa[top[x]];
int b=fa[top[y]];
if (dep[a]<dep[b])
swap(a,b),swap(x,y);
x=fa[top[x]];
}
return dep[x]<dep[y] ? x : y;
}
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
{
scanf("%d",&fa[i]);
if (!fa[i])
{
root=i;
continue;
}
link(i,fa[i]);
}
dfs(root,1);
cnt=0;
top[root]=root;
dfs2(root,root);
scanf("%d",&m);
while (m--)
{
scanf("%d%d",&x,&y);
x^=ans;
y^=ans;
ans=lca(x,y);
printf("%d\n",ans);
}
return 0;
}
然后是求值
【codevs2370】 小机房的树
求树上两点距离
我写了树链剖分
#include<algorithm> #include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include<cmath> using namespace std; #define N 100010 struct edge { int to,val,next; }e[N<<1]; int head[N<<1]; int cnt; int fa[N],dep[N],son[N],top[N],siz[N],pos[N]; int v1[N],v2[N]; int n,m; int u,v,w; int x,y; int root; void link(int x,int y,int z) { e[++cnt]=(edge){y,z,head[x]}; head[x]=cnt; } void dfs(int x,int d) { dep[x]=d; siz[x]++; for (int i=head[x];i;i=e[i].next) if (!dep[e[i].to]) { fa[e[i].to]=x; dfs(e[i].to,d+1); siz[x]+=siz[e[i].to]; v1[e[i].to]=e[i].val; if (siz[e[i].to]>siz[son[x]]) son[x]=e[i].to; } } void dfs2(int x,int d) { top[x]=d; if (son[x]) { v2[son[x]]=v2[x]+v1[son[x]]; dfs2(son[x],top[x]); } for (int i=head[x];i;i=e[i].next) if (e[i].to!=son[x] && e[i].to!=fa[x]) dfs2(e[i].to,e[i].to); } int lca(int x,int y) { int res(0); while (top[x]!=top[y]) { int a=fa[top[x]]; int b=fa[top[y]]; if (dep[a]<dep[b]) swap(a,b),swap(x,y); res+=v2[x]+v1[top[x]]; x=a; } if (dep[x]<dep[y]) swap(x,y); return res+v2[x]-v2[y]; } int main() { scanf("%d",&n); for (int i=1;i<n;i++) { scanf("%d%d%d",&u,&v,&w); link(++u,++v,w); link(v,u,w); } dfs(1,1); dfs2(1,1); scanf("%d",&m); while (m--) { scanf("%d%d",&x,&y); printf("%d\n",lca(++x,++y)); } return 0; }
复杂度O(qlogn),还是比较快的。
标签:
原文地址:http://www.cnblogs.com/yangjiyuan/p/5399655.html