HYBSZ 1787 紧急集合 LCA

HYBSZ 1787 题目描述:

 欢乐岛上有个非常好玩的游戏,叫做“紧急集合”。在岛上分散有N 个等待点,有N-1条道路连接着它们,每一条道路都连接某两个等待点,且通过这些道路可以走遍所有的等待点,通过道路从一个点到另一个点要花费一个游戏币。参加游戏的人三人一组,开始的时候,所有人员均任意分散在各个等待点上(每个点同时允许多个人等待),每个人均带有足够多的游戏币用于支付使用道路的花费)、地图(标明等待点之间道路连接的情况)以及对话机(用于和同组的成员联系)。当集合号吹响后,每组成员之间迅速联系,了解到自己组所有成员所在的等待点迅速在N 个等待点中确定一个集结点,组内所有成员将在该集合点集合,集合所用花费后,最少的组将是游戏的赢家。小可可和他的朋友邀请你一起参加这个游戏,由你来选择集合点,聪明的你能够完成这个任务,帮助小可可赢得游戏吗?
输入: 第一行两个正整数N 和M(N<=500000,M<=500000),之间J用一个空格隔开。分别表示等待点的个数(等待点也从1到N 进行编号)和获奖所需要完成集合的次数。
随后有N-1行,每行用两个正整数A 和B,之间用一个空格隔开,表示编号为A 和编号为B的等待点之间有一条路。
接着还有M 行,每行用三个正整数表示某次集合前小可可、小可可的朋友以及你所在等待点
的编号。
Sample Input6 41 22 32 44 55 64 5 66 3 12 4 46 6 6
Sample Output5 2 2 5 4 1 6 0

这道题目就是求三个点的最近公共祖先。假设三个点分别为 A , B , C , 设三个点的 LCA 是 ans , 那这个 ans 一定是 A B的最近公共祖先,AC的最近公共祖先,BC的最近公共祖先其中一个。所以分别找出 AB  AC  BC 的LCA, 然后和剩下那个点一起计算距离,满足距离最短的那个祖先就是。
因为有最多 500000个点,如果用 Tarjan 算法,很可能会超时。
所以我用新学的倍增法来计算最近公共祖先。

        

#include <bits/stdc++.h>
using namespace std ;
typedef pair<int,int> P ;
int N , Q , u , v , t , ans , pos ;
int head[500005] , k ;
int dep[500005] ;
int fa[500005][21] ;
struct Node{
	int v , next ;
} e[500005<<1] ;

void Add( int u , int v ){
	e[k].v = v , e[k].next = head[u] , head[u] = k++ ;
}

void BFS(){
	queue<int> One ;                         // BFS 找出每个点的深度 dep
	dep[1] = 1 ;
	One.push( 1 ) ;
	while( !One.empty() ){
		int u = One.front() ;
		One.pop() ;
		for( int i = head[u] ; i != -1 ; i = e[i].next ){
			int v = e[i].v ;
			if( dep[v] ) continue ;          // 如果这个点访问过了
			dep[v] = dep[u] + 1 ;
			fa[v][0] = u ;
			One.push( v ) ;
		}
	}
}

void Get_dp(){   
	int i , j ;
	for( j = 1 ; ( 1<<j ) <= N ; ++j )
		for( i = 1 ; i <= N ; ++i )
			if( fa[i][j-1] )
				fa[i][j] = fa[fa[i][j-1]][j-1] ;      // 获取倍增跳 2^j 的节点
}

int LCA( int u , int v ){
	if( dep[u] < dep[v] ) swap( u , v ) ;        
	int i ;
	for( i = 20 ; i >= 0 ; --i )
		if( dep[u] - (1<<i) >= dep[v] )        // 先把深度大的提高到和 v 同一层      
			u = fa[u][i] ;
	if( u == v ) return u ;
	for( i = 20 ; i >= 0 ; --i )
		if( fa[u][i] != fa[v][i] )   
			u = fa[u][i] , v = fa[v][i] ;   // 逐渐往最近公共祖先靠近
	return fa[u][0] ;   // 再往上走一层就是 LCA
}

void solve( int u , int v , int t ){
	int root1 = LCA( u , v ) ;
	int root2 = LCA( root1 , t ) ;  // ( dis[u]+dis[v]-2*dis[root1] ) + ( dis[root1]+dis[t]-2*dis[root2] )
	int len = dep[u] + dep[v] + dep[t] - dep[root1] - 2*dep[root2] ; 
	if( ans > len )
	    ans = len , pos = root1 ;   // 找最合适的 LCA
}

int main(){
	while( ~scanf( "%d%d" , &N , &Q ) ){
		memset( head , -1 , sizeof( head ) ) ;
		memset( fa , 0 , sizeof( fa ) ) ;
		memset( dep , 0 , sizeof( dep ) ) ;
		k = 0 ;
		for( int i = 1 ; i <= N-1 ; ++i ){
			scanf( "%d%d" , &u , &v ) ;
			Add( u , v ) ;
			Add( v , u ) ;
		}
		BFS() ;
		Get_dp() ;
		while( Q-- ){
			scanf( "%d%d%d" , &u , &v , &t ) ;
			ans = 0x3f3f3f3f ;
			solve( u , v , t ) ;                  // 两两求 LCA 
			solve( u , t , v ) ;
			solve( v , t , u ) ;
			printf( "%d %d\n" , pos , ans ) ;
		}
	}
	return 0 ;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值