POJ 1984 Distance Queries LCA

http://poj.org/problem?id=1986

题意:给你一颗树,任意给定Q次询问(a,b) ,询问a 和b两个结点之间的距离。N<=40000 , Q<=40000

思路:LCA,因为是一颗树,我们可以任意选取一个结点作为树的根,然后用一次dfs,在O(E)的时间内求出所有点到根的距离,然后用LCA的Tarjin离线算法求出Q次询问的最近公共祖先,最后dis[i][j] = dis[i] + dis[j] - dis[ LCA(i,j) ] ;dis[i] 为i到根的距离。本题的方向可以无视。

实现一:Tarjin离线算法,复杂度为:O(N+Q)    219ms

代码:

#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std;
const int MAXN = 40010 ;
int N , M ,Q;
struct Node{
	int num ,next ;
	int dis ;
}edge[MAXN*2] ;
int root[MAXN] , cnt ;
int f[MAXN] ;
int dis[MAXN] ;
bool vis[MAXN] ;
int p[MAXN] ;

struct Node1{
	int s,e ;
	int next ;
	int f  ;
}e2[10010*2] ;
int root2[MAXN] ,c2 ;
int ans[MAXN] ;

void add(int a, int b , int c){
	edge[cnt].num = b;
	edge[cnt].next = root[a] ;
	edge[cnt].dis = c ;
	root[a] = cnt++ ;
}
void Build(int u){
	for(int i=root[u] ;i!=-1;i=edge[i].next){
		int v = edge[i].num;
		if(v == f[u])	continue ;
		f[v] = u ;
		Build(v) ;
	}
}
void dfs(int u){
	for(int i=root[u] ;i!=-1; i=edge[i].next){
		int v = edge[i].num;
		if(v == f[u])	continue ;
		dis[v] = dis[u] + edge[i].dis ;
		dfs(v) ;
	}
}
void add2(int a ,int b ,int c){
	e2[c2].e = b ;
	e2[c2].s = a ;
	e2[c2].next = root2[a] ;
	e2[c2].f = c ;
	root2[a] = c2++ ;
}
int find(int a){
	if(a != p[a]){
		p[a] = find( p[a] ) ;
	}
	return p[a] ;
}
void tarjin(int u){
	vis[u] = 1;
	for(int i=root2[u] ;i!=-1;i=e2[i].next){
		int v = e2[i].e ;
		int f = e2[i].f ;
		if(vis[v]){
			ans[f] = find(v) ;	
		}
	}
	for(int i=root[u] ;i!=-1;i=edge[i].next){
		int v = edge[i].num ;
		if(v == f[u])	continue ;
		if(!vis[v]){
			tarjin(v); 	
			p[v] = u ;
		}
	}	
}
int main(){
	int a,b ,c ,ff;
	char ch[4] ;
	while(scanf("%d %d",&N,&M) == 2){
		memset(root , -1, sizeof(root));
		cnt = 0 ;
		for(int i=1;i<=M;i++){
			scanf("%d %d %d %s",&a,&b,&c,ch);
			add(a,b,c) ;	add(b,a,c) ;
		}	
		memset(f ,-1 ,sizeof(f)) ;
		Build(1);
		dis[1] = 0;
		dfs(1) ;
		memset(root2, -1 , sizeof(root2));
		c2 = 0 ;
		scanf("%d",&Q);
		for(int i=1;i<=Q;i++){
			scanf("%d %d",&a,&b);
			add2(a,b,i);	add2(b,a,i) ;
		}
		memset(vis, 0 ,sizeof(vis));
		for(int i=1;i<=N;i++)	p[i] = i ;
		tarjin(1) ;
		for(int i=0;i<c2;i+=2){
			a = e2[i].s ;
			b = e2[i].e ;
			ff = e2[i].f ;
			int res = dis[a] + dis[b] - 2 * dis[ ans[ff] ] ;
			printf("%d\n",res); 
		}
	}	
	return 0 ;
}

实现二:dfs +rmq ,复杂度:O( 2*N*log(2*N) ) + O(1)     375ms

代码:

/*
R[MAXN] : 每个结点第一次被遍历的下标
E[MAXN] : dfs序列
dep[MAXN] : dfs遍历结点深度 
算法描述:
(1)DFS:从树T的根开始,进行深度优先遍历,并记录下每次到达的顶点。
	第一个的结点是root(T),每经过一条边都记录它的端点。由于每条边
	恰好经过2次,因此一共记录了2n-1个结点,用E[1, ... , 2n-1]来表示。
(2)计算R:用R[i]表示E数组中第一个值为i的元素下标,即如果R[u] < R[v]
	时,DFS访问的顺序是E[R[u], R[u]+1, ..., R[v]]。虽然其中包含u的后
	代,但深度最小的还是u与v的公共祖先。
(3)RMQ:当R[u] ≥ R[v]时,LCA[T, u, v] = RMQ(L, R[v], R[u]);否
	则LCA[T, u, v] = RMQ(L, R[u], R[v]),计算RMQ。由于RMQ中使用的ST
	算法是在线算法,所以这个算法也是在线算法。
*/
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#define MIN(a,b) (a)>(b)?(b):(a)
const int MAXN = 40000+10 ;
int N ,M , Q ;
struct Edge{ int num,next,dis ;}edge[MAXN*2] ;
int root[MAXN] , cnt ;
bool vis[MAXN] ;
int R[MAXN] , E[MAXN*2+1] ,dep[MAXN*2+1] ,idx;
int dp[2*MAXN+1][22] ;
int d[22] ,dis[MAXN];

void Init(){
	memset(root , -1 ,sizeof(root));
	memset( vis , 0,sizeof(vis) );
	cnt = 0 ;		
}
void add_edge(int a, int b ,int c){
	edge[cnt].num = b ;
	edge[cnt].next = root[a] ;
	edge[cnt].dis = c ;
	root[a] = cnt++ ;
} 
void dfs(int u ,int d){
	vis[u] = 1 ;
	R[u] = idx ; E[idx] = u ; dep[idx++] = d ;
	for(int i=root[u] ;i!=-1;i=edge[i].next){
		int v = edge[i].num;
		if(vis[v] == 0){
			dfs(v, d+1);
			E[idx] = u ; dep[idx++] = d ;
		}
	}
}
void cal(int u){
	vis[u] = 1;
	for(int i=root[u] ;i!=-1;i=edge[i].next){
		int v= edge[i].num;
		int d = edge[i].dis  ;
		if(vis[v] == 0){
			dis[v] = dis[u] + d ;
			cal(v) ;
		}
	}
}
void Init_Rmq(){
	int i , j ;
	for(d[0]=1,i=1;i<22;i++)d[i]=d[i-1]*2 ;
	for(i=1;i<idx;i++)	dp[i][0] = i ; 
	
	for(j=1;(1<<j)<idx;j++){
		for(i=1;i+(1<<j)<idx;i++){
			int idx1 = dp[i][j-1] ;
			int idx2 = dp[i+(1<<(j-1))][j-1] ;
			if(dep[ idx1 ] > dep[ idx2 ]){
				dp[i][j] = idx2 ;
			}
			else{
				dp[i][j] = idx1 ;
			}
		}
	}
}
int main(){
	int i,j,a,b,c;
	char ch[5] ;
	while(scanf("%d %d",&N,&M) == 2){
		Init() ;		
		for(i=0;i<M;i++){
			scanf("%d %d %d %s",&a,&b,&c,ch);
			add_edge(a,b,c);	add_edge(b,a,c) ;
		}
		idx = 1 ;
		memset(vis , 0, sizeof(vis));
		dis[1] = 0 ;
		cal(1);
		memset(vis , 0, sizeof(vis));
		dfs(1,0);
		Init_Rmq() ;
		scanf("%d",&Q);
		for(i=0;i<Q;i++){
			scanf("%d %d",&a,&b);
			int fa = R[a] ;
			int fb = R[b] ;
			if(fa > fb){
				std::swap(fa, fb) ;
			}
			int k = int(log(double(fb-fa+1)) / log(2.0)) ;
			int idx1 = dp[ fa ][k] ;
		 	int idx2 = dp[ fb-(1<<k)+1 ][k] ; 
			if(dep[ idx1 ] > dep[ idx2 ])	c = idx2 ;
			else	c = idx1 ;
			printf("%d\n",dis[a]+dis[b]-2*dis[ E[c] ]);
		}
	}	
	return 0 ;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值