第一次接触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 ;
}