Codeforces 832D Misha, Grisha and Underground 用 LCA 求相交距离

题目描述:Codeforces 832D

Misha and Grisha are funny boys, so they like to use new underground. The underground has n stations connected with n - 1 routes so that each route connects two stations, and it is possible to reach every station from any other.

The boys decided to have fun and came up with a plan. Namely, in some day in the morning Misha will ride the underground from station s to station f by the shortest path, and will draw with aerosol an ugly text "Misha was here" on every station he will pass through (including s and f). After that on the same day at evening Grisha will ride from station t to station f by the shortest path and will count stations with Misha's text. After that at night the underground workers will wash the texts out, because the underground should be clean.

The boys have already chosen three stations ab and c for each of several following days, one of them should be station s on that day, another should be station f, and the remaining should be station t. They became interested how they should choose these stations sft so that the number Grisha will count is as large as possible. They asked you for help.

Input

The first line contains two integers n and q (2 ≤ n ≤ 1051 ≤ q ≤ 105) — the number of stations and the number of days.

The second line contains n - 1 integers p2, p3, ..., pn (1 ≤ pi ≤ n). The integer pimeans that there is a route between stations pi and i. It is guaranteed that it's possible to reach every station from any other.

The next q lines contains three integers ab and c each (1 ≤ a, b, c ≤ n) — the ids of stations chosen by boys for some day. Note that some of these ids could be same.

Output

Print q lines. In the i-th of these lines print the maximum possible number Grisha can get counting when the stations st and f are chosen optimally from the three stations on the i-th day.

Example
Input
3 2
1 1
1 2 3
2 3 3
Output
2
3
Input
4 1
1 2 3
1 2 3
Output
2

这道题目意思就是在一棵生成树上,求三个点之间最大的相交距离,或者说 其中两个点到第三个点的路径上重复了多少个点。

先给出一张图:

如果先访问 A , O 是 BC的LCA ;如果先访问B , O 是AC的LCA;如果先访问C,O是AB的LCA


这张图的三条边的边长可以是 0 , 也就是如果是 0 ,点就会重合;其他加上其它分支不影响。在这张图中,我们要求的就是中间那个分岔口,然后求这个分岔口到 A B C 距离的最大值+1(求点,不是求边,但是点数 = 边数+1)。在这张图中,假设 A 是终点也就是要求 B->A 和 C->A 路径的共同部分,这段距离用数学公式就是:OA  =  ( AB + AC - OB - OC ) / 2 = ( AB + AC - BC )有些部分可以是 0 , 或者认为是长度为 0 的虚拟段;同样的,如果 B 是终点,公式就是  OB = ( AB + BC - AC ) / 2 ;如果C 是终点,公式就是 OC = ( BC + AC - AB ) / 2 。所以说最大值一定产生在这三个当中。

求两点间距离可以用 LCA 来做,O 是 AB 或 BC 或AC 的LCA ,因为边可以是  0 , 允许重合,公式中的一些相同项会抵消掉,或者说没影响,不改变正确性,比如  OC = 0 , C 和 O 重合,那么 BC = BO , AC = AO ,相应变短。

当然,得满足是连通的树,这样,树中任意三个点都满足上面的结构。

#include <bits/stdc++.h>
using namespace std ;
int head[100005] , k ;
int dep[100005] ;
int fa[100005][21] ;
struct Node{
	int v , next ;
} e[100005<<1] ;
int N , Q ;

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

void BFS(){
	queue<int> One ;
	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 ){    // BFS 求 dep 深度
			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] ;       // 获取倍增信息
}

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

int dis( int u , int v ){
	return dep[u] + dep[v] - 2*dep[LCA( u , v)] ;  // 求树中两点间距离
}

int main(){
	int i , u , v , t ;
	while( ~scanf( "%d%d" , &N , &Q ) ){
		memset( head , -1 , sizeof( head ) ) ;
		memset( dep , 0 , sizeof( dep ) ) ;
		memset( fa , 0 , sizeof( fa ) ) ;
		k = 0 ;
		for( i = 2 ; i <= N ; ++i ){
			scanf( "%d" , &u ) ;
			Add( u , i ) ;
			Add( i , u ) ;
		}
		BFS() ;
		Get_dp() ;
		while( Q-- ){
			scanf( "%d%d%d" , &u , &v , &t ) ;
			int ans1 = dis( u , v ) ;          // 取出这三段
			int ans2 = dis( u , t ) ;
			int ans3 = dis( v , t ) ;
			int ans = max( ( ans1+ans2-ans3 )/2 , max( ( ans1+ans3-ans2 )/2 , ( ans2+ans3-ans1 )/2 ) ) ;
			cout << ans + 1 << endl ;
		}
	}
	return 0 ;
}


还是这张图 ,注意有的边可以是 0 , 可以缩成点 , ABC变成直线之类的。

如果先访问 A , O 是 BC的LCA ;如果先访问B , O 是AC的LCA;如果先访问C,O是AB的LCA


我们可以发现,假设先访问 A , 后访问 OBC , 那么 BC 的最近公共祖先就是 O, O可能和 A重合,dep[ A ] <= dep[ O ] ;AB 和 AC 的最近公共祖先都是 A   ; 假设先访问 B , 要求的就是 BO 段,依旧满足dep[ B ] <= dep[ O ] , O是深度最大的LCA;假设先访问 C , 要求的就是CO,依旧满足 dep[ C ] <= dep[ O ] ,O是深度最大的。

这和上面的数学公式是一致的,最后在 OA OB  OC 中取最大的。而且,可以发现,无论先放问谁,O是这些 LCA 中 dep 值最大的,也就是深度更大的,在树中,两个点只会有一个LCA(如果有两个,就会形成环),就是 O 。

还是注意普遍性,上图的边可以是 0 ,三个点都可以 和 O重合 。

注意+1 , 因为是求点,n-1 条边,在树中只能是  n 个点,之前我们求的距离都是边 。

所有的都是为了求 O ,按照访问顺序,深度最大的点,然后从 O出发到其他三个点 的距离,取最大值+1 


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

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

void BFS(){
	queue<int> One ;
	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 )
				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 = 20 ; i >= 0 ; --i )
		if( dep[u] - (1<<i) >= dep[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] ;
}

int dis( int u , int v ){
	return dep[u] + dep[v] - 2*dep[LCA( u , v)] ;  // 求两点间距离
}

int main(){
	int i , u , v , t ;
	while( ~scanf( "%d%d" , &N , &Q ) ){
		memset( head , -1 , sizeof( head ) ) ;
		memset( dep , 0 , sizeof( dep ) ) ;
		memset( fa , 0 , sizeof( fa ) ) ;
		k = 0 ;
		for( i = 2 ; i <= N ; ++i ){
			scanf( "%d" , &u ) ;
			Add( u , i ) ;
			Add( i , u ) ;
		}
		BFS() ;
		Get_dp() ;
		while( Q-- ){
			scanf( "%d%d%d" , &u , &v , &t ) ;
			int ans1 = LCA( u , v ) ;
			int ans2 = LCA( u , t ) ;
			int ans3 = LCA( v , t ) ;
			if( dep[ans2] > dep[ans1] ) ans1 = ans2 ;   // 找深度最大的那个 LCA 就是 O
			if( dep[ans3] > dep[ans1] ) ans1 = ans3 ;  
			int ans = max( dis( ans1 , u ) , max( dis( ans1 , v ) , dis( ans1 , t ) ) ) ;
			cout << ans + 1 << endl ;
		}
	}
	return 0 ;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值