首页 > 代码库 > 最近公共祖先(LCA)
最近公共祖先(LCA)
最近公共祖先(LCA)
by mps
Define:求树上两个点的祖先中里两个点最近的一个点,该点称为这两个点的最近公共祖先(英译LCA<Lowest Common Ancestors>)。
那么,如何求LCA呢?
经过思考,不难发现,有一种暴力方法,我们对于这两个点不断BFS,直到出现一个相同的点,该点即为LCA,空间如果跟不上的话可以改为迭代加深搜索
时间复杂度O(N2),对于大一点的数据,会TLE的,我们追求完美,还有更好的算法。
算法改进
我们可以先求出两个节点在这颗树中的深度(D1,D2),然后由偏低的上升至偏高的,在一起走,直到出现重合,重合点为LCA
为什么这种算法可行呢?显然可以发现想要走到共同点,需要高度一样,所以就有了求深度。一起走所消耗的时间最少(这不用证明了吧?)
求深度DFS即可,时间复杂度O(NlogN),加上每次在线求LCA的时间为O(logN),所以该算法的时间复杂度为(NlogN+QlogN) (Q为询问次数)
怎么一起走?怎么上升?
我们可以设p(i,j)来表示第i个节点向上走2^j步的祖先,那样我们就可以不断倍增,直到高度≥初始偏高的点,然后一起倍增,直到倍增的点一样,即该祖先为
LCA
应用实例
货车运输(NOIP2013提高组Day1 T3)
【问题描述】
A国有 n 座城市,编号从1 到n,城市之间有 m条双向道路。每一条道路对车辆都有重量限制,简称限重。
现在有 q 辆货车在运输货物,司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。
【输入】
输入文件名为 truck.in。
输入文件第一行有两个用一个空格隔开的整数 n,m,表示 A 国有 n 座城市和 m 条道路。
接下来 m 行每行 3 个整数 x、y、z,每两个整数之间用一个空格隔开,表示从 x 号城市到 y号城市有一
条限重为 z 的道路。注意:x 不等于 y,两座城市之间可能有多条道路。
接下来一行有一个整数 q,表示有 q 辆货车需要运货。
接下来 q 行,每行两个整数 x、y,之间用一个空格隔开,表示一辆货车需要从 x 城市运输货物到 y 城市
注意:x 不等于 y。
【输出】
输出文件名为 truck.out。
输出共有 q 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。如果货车不能到达目的地,输
出-1
【输入输出样例】
truck.in truck.out
4 3 3
1 2 4 -1
2 3 3 3
3 1 1
3
1 3
1 4
1 3
【数据范围】
对于60%的数据,n<1,000
对于100%的数据,n<10,000,m<50,000
【分析】
一开始拿到这题以为dinic+最大流→_→,看到数据就心灰意冷了,承受不了,我们来简单分析一下:
题目要求我们找到两个点最大边的最小权值,很明显除了最大连通边以外的边全是无用的,那么我们
可以使用最大生成树来删掉多余的边(说的严谨一点,就是保留最大边),然后在这颗树上寻找两点
的之间的边的最小权值,注意到了“最小”,而且时间要求很严,这时我们想到了LCA,两个点的边
的最小权值边不就是他们任一点的到他们的LCA的边中的最小的吗?而且LCA时间消耗还正好符合本题
所以采用LCA(树上倍增)+MST+DP来解决这题
【代码】
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #include <cstring> 5 using namespace std; 6 7 const int MaxM=108888; 8 const int MaxN=10888; 9 10 int n,m,q; 11 12 struct data{ 13 int u,v,w; 14 friend bool operator< (data a,data b){return a.w>b.w;}//BST 15 }a[MaxM]; 16 17 struct list{ 18 int to,next,w; 19 }e[MaxM]; 20 21 int head[MaxN],cnt=0; 22 int t[MaxN],deep[MaxN],p[MaxN][21],d[MaxN][21]; 23 //PS:p(i,j)表示i的第2^j的祖先,d(i,j)表示i走到2^j的祖先的路途中最小权边 24 25 void addedge(int u,int v,int w){ 26 cnt++; 27 e[cnt].to=v; 28 e[cnt].next=head[u]; 29 e[cnt].w=w; 30 head[u]=cnt; 31 } 32 33 void init(){ 34 freopen("truck.in","r",stdin); 35 freopen("truck.out","w",stdout); 36 scanf("%d%d",&n,&m); 37 int i; 38 for(i=1;i<=m;i++)scanf("%d%d%d",&a[i].u,&a[i].v,&a[i].w); 39 memset(t,-1,sizeof(t)); 40 memset(deep,0,sizeof(deep)); 41 } 42 43 int findR(int x){return (t[x]==-1?x:t[x]=findR(t[x]));} 44 45 void Kruskal(){ 46 int X,Y,i=1,k=0; 47 sort(a+1,a+m+1); 48 while(k<n-1 && i<=m){ 49 X=findR(a[i].u); 50 Y=findR(a[i].v); 51 if(X!=Y){ 52 t[X]=Y; 53 addedge(a[i].u,a[i].v,a[i].w); 54 addedge(a[i].v,a[i].u,a[i].w); 55 k++; 56 } 57 i++; 58 } 59 } 60 61 void dfs(int u){ 62 int i; 63 for(i=1;i<=16;i++) 64 { 65 if(deep[u]<(1<<i))break; 66 p[u][i]=p[p[u][i-1]][i-1]; 67 d[u][i]=min(d[u][i-1],d[p[u][i-1]][i-1]); 68 } 69 for(i=head[u];i;i=e[i].next) 70 if(!deep[e[i].to]){ 71 deep[e[i].to]=deep[u]+1; 72 p[e[i].to][0]=u; 73 d[e[i].to][0]=e[i].w; 74 dfs(e[i].to); 75 } 76 } 77 78 int lca(int u,int v){ 79 if(deep[u]<deep[v])swap(u,v); 80 int c=deep[u]-deep[v],i; 81 for(i=0;i<=16;i++) 82 if((1<<i)&c)u=p[u][i]; 83 for(i=16;i>=0;i--) 84 if(p[u][i]!=p[v][i]){ 85 u=p[u][i]; 86 v=p[v][i]; 87 } 88 if(u==v)return u; 89 else return p[u][0]; 90 } 91 92 int query(int u,int v){ 93 int i,res=0x3f3f3f3f,c=deep[u]-deep[v]; 94 for(i=0;i<=16;i++) 95 if(c&(1<<i)){ 96 res=min(res,d[u][i]); 97 u=p[u][i]; 98 } 99 return res;100 }101 102 void solve(){103 Kruskal();104 int i,j;105 for(i=1;i<=n;i++)106 if(!deep[i]){107 p[i][0]=i;108 d[i][0]=0x3f3f3f3f;109 deep[i]=1;110 dfs(i);111 }112 scanf("%d",&q);113 int top,u,v;114 while(q--){115 scanf("%d%d",&u,&v);116 if(findR(u)!=findR(v))printf("-1\n");117 else {118 top=lca(u,v);119 printf("%d\n",min(query(u,top),query(v,top)));120 }121 }122 }123 124 int main(){125 init();126 solve();127 return 0;128 }
最近公共祖先(LCA)