SCU 3365 学习 LCA 的倍增做法

第一次接触LCA,是在 SCU 3365 , 模板题,当时我是用 Disjkstra 加记忆做的,还不会 LCA。后来学习 tarjan 算法,其实还是深搜,当时处理并记录,Tarjan 用来写 hdu 2586 还是过得去,但是在 SCU 3365 却一直超时,可能 tarjan 算法在某些数据上效率很低吧。

今天学习 LCA 的倍增做法: SCU 3365

先 DFS 或者 BFS 找到每个节点当前的父亲,然后 dp 处理,用一个数组记录往上跳 2^j 个记录的节点,所以可以跳的很快,一般的数据 18 次或者 20 次就够了。求两点的最近公共祖先,倍增做法是先把深度更大的,也就是树从起点往下看更低的点,提到(也可以倍增跳)和另一个点同一高度,然后再 2^j 个步数地同时往上跳,保持一致,最后找出 LCA。

求向上跳 2^j 个节点:

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] ;          
}

可以看到最多可以跳 j 次 ,满足 ( 1<<j ) <= N ,这是第一层 for 循环

第二层 for 循环,每一次跳都更新每一个点,可以跳 2^j 个节点 , 递推在于每次都引用了上次的父亲 fa[i][j-1] 设为 u,还用上了这个父亲跳 2^(j-1)步可以跳到的位置 fa[u][j-1]。这个 fa[i][j-1] 是 i 跳 2^(j-1) 可以到的位置,在这个位置u上再继续跳 2^(j-1) 步,加起来相对于 i 就是 2^j 步。所以每次都 *2 ,倍增就体现在这里。(本人初学,如有错误,请指正)

SCU 3365   AC代码如下:

#include <bits/stdc++.h>    // 如果 SCU 不支持, 就改成正常的 iostream , cstring , cstdio
using namespace std ;
int head[1005] , k ;
int dis[1005] ;
int dep[1005] ;
int fa[1005][20] ;
int N , Q ;
struct Node{
	int v , cost , next ;
} e[1005<<1] ;

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

void DFS( int u ){
	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 ;                 // 更新一层的深度, 方便后面倍增跳
		dis[v] = dis[u] + e[i].cost ;    
		fa[v][0] = u ;
		DFS( 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] ;          // 每次倍增地跳
}

int LCA( int u , int v ){
	if( dep[u] < dep[v] ) swap( u , v ) ;             // 先找到深度更大的, 也就是更低的点
	int i ;
	for( i = 18 ; i >= 0 ; --i )
		if( dep[u] - (1<<i) >= dep[v] )           // 把更低的点 u 提到和 v 同一高度
			u = fa[u][i] ;
	if( u == v ) return u ;
	for( i = 18 ; i >= 0 ; --i )
		if( fa[u][i] && fa[u][i] != fa[v][i] )    // 同一高度后, 同时跳
			u = fa[u][i] , v = fa[v][i] ;
	return fa[u][0] ;
}

int main(){
	// freopen( "SCU 3365.txt" , "r" , stdin ) ;
	int i , u , v , cost ;
	while( ~scanf( "%d%d" , &N , &Q ) ){
		memset( head , -1 , sizeof( head ) ) ;
		memset( fa , 0 , sizeof( fa ) ) ;
		memset( dep , 0 , sizeof( dep ) ) ;
		k = 0 ;
		for( i = 1 ; i <= N-1 ; ++i ){
			scanf( "%d%d%d" , &u , &v , &cost ) ;
			Add( u , v , cost ) ; 
			Add( v , u , cost ) ;
		}
		dep[1] = 1 , dis[1] = 0 ;  // 初始化起点信息
		DFS( 1 ) ;                 // 获取每个点的 fa[i][0], 前驱, 更新距离 dis
		Get_dp() ;                 // 获取每个节点向上跳 2^i 的点
		while( Q-- ){
			scanf( "%d%d" , &u , &v ) ;
			cout << dis[u] + dis[v] - 2*dis[LCA( u , v )] << endl ;
		}
	}
	return 0 ;
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值