最近公共祖先LCA:RMQ转化

19 篇文章 0 订阅
1,最近公共祖先(LCA): 
对于有根树T的两个结点u、v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u、v的祖先且x的深度尽可能大。
2,LCA问题向RMQ问题的转化方法:(RMQ返回最值的下标) 
对树进行深度优先遍历,每当“进入”或回溯到某个结点时,将这个结点的深度存入数组dfsNum最后一位。同时记录结点i在数组中第一次出现的位置(事实上就是进入结点i时记录的位置),记做first[i]。如果结点dfsNum[i]的深度记做depth[i],易见,这时求LCA(u,v),就等价于求 E[RMQ(depth,first[u],first[v])],(first[u]<first[v])。 
例如: 

 

(深度遍历)difNum[i]为:1,2,1,3,4,3,5,3,1 
first[i]为:0,1,3,4,6 
(个点对应的深度)depth[i]为:0,1,0,1,2,1,2,1,0 

于是有: 
LCA(4,2) = difNum[RMQ(D,first[2],first[4])] = difNum[RMQ(D,1,6)] = difNum[2] = 1 

转化后得到的数列长度为树的结点数*2-1(每经过一条边,都会记录端点,有N-1条边,所以会回溯N-1次。且每个顶点都会被添加一次,所以长度为2N-1) 

转自:http://kmplayer.iteye.com/blog/604232


//poj 1330 lca&&rmq 在线算法
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
const long M=31000;

struct LCA{
	long head[M],to[M],next[M],edge_cnt;
	long dfsNum[M],depth[M],first[M],dfs_cnt;
	long rmq[M][30],n;

	void init_edge(){
		memset(head,-1,sizeof(head));
		edge_cnt=0;  dfs_cnt=0;
	}
	void add_edge(long u,long v){
		next[edge_cnt]=head[u]; to[edge_cnt]=v; head[u]=edge_cnt++;
	}

	void dfs(long u,long fa,long dep){
		dfsNum[dfs_cnt]=u;
		depth[dfs_cnt]=dep;
		first[u]=dfs_cnt++;

		for (long i=head[u];i!=-1;i=next[i]){
			long v=to[i];
			if (v==fa) continue;
			dfs(v,u,dep+1);
			dfsNum[dfs_cnt]=u;
			depth[dfs_cnt]=dep;
			dfs_cnt++;
		}
	}

	void init_rmq(){
		n=dfs_cnt;
		for (long i=0;i<n;++i) rmq[i][0]=i;
		for (long j=1;(1<<j)<=n;++j)
			for (long i=0;i+(1<<j)-1<n;++i)
				rmq[i][j]=(depth[rmq[i][j-1]]<depth[rmq[i+(1<<(j-1))][j-1]])?
				rmq[i][j-1]:rmq[i+(1<<(j-1))][j-1];
	}
	long rmqIndex(long l,long r){
		if (l>r) std::swap(l,r);
		 int k=(int)(log((r-l+1)*1.0)/log(2.0));  
		return (depth[rmq[l][k]]<depth[rmq[r-(1<<k)+1][k]])?
			rmq[l][k]:rmq[r-(1<<k)+1][k];
	}

	long lca(long u,long v){
		return dfsNum[rmqIndex(first[u],first[v])];
	}
}G;

long t,vis[M],root;
int main(){
	scanf("%d",&t);
	while (t--){
		long n;
		G.init_edge();
		scanf("%d",&n);
		long u,v;
		memset(vis,0,sizeof(vis));
		for (long i=1;i<n;++i){
			scanf("%d%d",&u,&v);
			G.add_edge(u,v);
			//G.add_edge(v,u);
			vis[v]=1;
		}
		for (long i=1;i<=n;++i)
			if (!vis[i]){ root=i; break;}
		G.dfs(root,-1,0);
		G.init_rmq();
		scanf("%d%d",&u,&v);
		printf("%d\n",G.lca(u,v));
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值