【LCA】洛谷P4281 [AHOI2008]紧急集合 / 聚会

38 篇文章 0 订阅

题目

Sample-in
6 4  
1 2  
2 3  
2 4 
4 5
5 6
4 5 6
6 3 1
2 4 4 
6 6 6
Samplee-out
5 2
2 5
4 1
6 0

思路

已知这个图是一颗树,然后要求三个点到某个点的距离总和最少。
两个点的话可以找最近公共祖先,用LCA,即倍增求最近公共祖先。
三个点的情况根据模拟可以得到:至少有 两组的最近公共祖先是相同的,如下图lca(x,z)=lca(y,z)。而最优解是走到x,y的最近公共祖先上,因为这个祖先深度最深,可以牺牲z一个人的距离缩短xy两个人跑的距离。

同理,其它情况也是这样。所以我们可以求三次LCA,比较一下。有两次结果祖先的点必定是较潜的点,剩下的那个祖先就是答案。最后用深度计算下路程即可。


代码

#include<cstdio>
int n,m,t,jump[500001],Deep[500001],father[500001][51],l[500001],AAA1,AAA2,AAA3;
struct asdf{
	int to,next;
} A[1000001];
void read(){   //读入
	int x,y;
	scanf("%d%d",&n,&m);
	for(int i = 1; i < n; ++i){
		scanf("%d%d", &x, &y);
		A[++t] = (asdf){y,l[x]}; l[x] = t;  //邻接表 
		A[++t] = (asdf){x,l[y]}; l[y] = t;
	}
	int k = 1;
	jump[1] = 1;    //jump[i]为i在2的几次方内
	for(int i = 2; i <= n; ++i){
		jump[i] = jump[i-1];
		if(i / k == 2){
			k*=2;
			++jump[i];
		}
	}
}
void dfs(int now,int fu){   //dfs求深度和倍增父亲
	father[now][0] = fu;  //往前跳1步
	Deep[now] = Deep[fu] + 1;   //深度+1
	for(int i = 1; i <= jump[Deep[now]]; ++i)
	  father[now][i] = father[father[now][i-1]][i-1]; 
	    //往前跳2^i步=往前跳了2^(i-1)步后再跳2^(i-1)步得到的点 
	for(int i = l[now]; i; i = A[i].next) 
	  if(Deep[A[i].to] == 0) dfs(A[i].to,now);
}
int kmp(int x,int y){
	if(Deep[y]>Deep[x])  //先让两个点都跳的同一高度,再一起倍增跳跃
	  while(Deep[x] != Deep[y]) y = father[y][jump[Deep[y] - Deep[x]] - 1];
	else 
	  while(Deep[x] != Deep[y]) x = father[x][jump[Deep[x] - Deep[y]] - 1];
	if(x == y) return x; 
	for(int i = jump[Deep[x]]-1; i>=0; --i)  //从最大能跳的步数跳起,节省时间方便计算
	    if(father[x][i] != father[y][i]){    //如果这个高度还没到达公共祖先就跳过去
	    	x = father[x][i];
	    	y = father[y][i]; 
	    }
	return father[x][0];
}
void work(){
	int x,y,z;
	scanf("%d%d%d",&x,&y,&z);
	AAA1 = kmp(x,y); //如上文
	AAA2 = kmp(x,z);
	AAA3 = kmp(y,z);
	if(AAA1 == AAA2) printf("%d ",AAA3); 
	else if(AAA2 == AAA3) printf("%d ",AAA1);
	else if(AAA1 == AAA3) printf("%d ",AAA2);
	printf("%d\n",Deep[z]+Deep[y]+Deep[x]-Deep[AAA3]-Deep[AAA2]-Deep[AAA1]);  //计算
}
int main(){
	read();
	dfs(1,0);
	while(m--) work();
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值