CS Academy E.Triplet Min Sum(最近公共祖先 倍增法模板)

53 篇文章 1 订阅
50 篇文章 0 订阅

Triplet Min Sum

Time limit:  2500 ms
Memory limit:  128 MB

You are given a tree with NN nodes. Answer QQ queries of the type:

  • Given three distinct nodes AABB, and CC, find the node DD such that the sum of distances from DD to AABB and CC is minimum.

Standard input

The first line contains two integers NN and QQ.

Each of the following N-1N1 lines contain two integers, representing two nodes that share an edge.

Each of the following QQ lines contains three integers AABB and CC.

Standard output

For each query print two numbers on a distinct line: the node DD and the sum of distances from DD to AABB and CC.

Constraints and notes

  • 3 \leq N \leq 10^53N105 
  • 1 \leq Q \leq 10^51Q105 
  • Node DD can be equal to one of AABB or CC 
  • It can be proved the answer is unique
Input Output
10 3
1 7
4 7
6 2
8 3
9 8
5 8
2 4
3 4
10 7
1 7 8
8 3 10
6 10 3
7 4
3 4
4 5
题目大意:存在n个点和n-1条边,有Q次询问,每次询问abc三个点,要求输出这三点的最短路到达哪个点,长度为多少
解题思路:这是一道比较标准的最近公共祖先的模板题,所以就根据这道题总结一下这类算法,做这道题采用的方法是倍增法,思路如下
1.建图完成后,我们根据从其中任意点开始标记层次,最近公共祖先的原理就是根据要求找到最新公共祖先的两个点所在的层次,层次高的往上跳,直到跳到两个点的层次相同,之后大家一块跳,所以根据这个我们可以需要用dfs遍历图完成层次结构的设计
2.我们设置数组fa[i][j]意味着i的i+2^j位置是谁,j为0就是指的是i这个点的父亲是谁,同时我们要初始化fa[i][0]的值,也就是初始化每个点的父亲是谁,这一步会在dfs的时候完成,然后通过两层循环完成所有位置的对于他2^j位置的点的标记
for(j=1;(1<<j)<=n;j++)
	{
		for(i=1;i<=n;i++)
		{
			fa[i][j]=fa[fa[i][j-1]][j-1];
		}
	}

这段代码的意思指的是i的i+2^j位置应该是i+2^(j-1)位置的点的2^(j--1)决定,这个循环代码是倍增法的主要思想之一
3.之后我们就可以比较两个点的最近公共祖先是谁了
int f=dis[x]-dis[y];
//	cout<<f<<endl;
	for(i=0;(1<<i)<=f;i++)
	{
		if((1<<i)&f)
			x=fa[x][i];
	}

f指的是x和y这两个点之间差的层数,然后根据二进制将层次高的点不断地上移,直到两个点到达同一个层次
if(x!=y)
	{
		for(i=(int)log2(n);i>=0;i--)
		{
			if(fa[x][i]!=fa[y][i])
			{
				x=fa[x][i];
				y=fa[y][i];
			}
		}
		x=fa[x][0];
	}
这段代码表示的是,当两个点到达同一个层次之后,要是不相同,我们要做的是一起跳,直到只需要一就相同的位置就停止
以上就是LCA的主要内容
对于这道题要注意的是,我们要分别寻找xy,xz,yz三个部分的分别公共祖先是谁,结果会存在着有两个部分的最近公共祖先相同,但是我们不选择,因为最近公共祖先肯定是至少层次要低于或者等于某个点,这意味着对于三个点来说,他一定不是最短的路径,所以不选择相同的那个
#include<iostream>    
#include<cstdio>  
#include<stdio.h>  
#include<cstring>    
#include<cstdio>    
#include<climits>    
#include<cmath>   
#include<vector>  
#include <bitset>  
#include<algorithm>    
#include <queue>  
#include<map> 
#define inf 9999999; 
using namespace std;


int fa[100005][30],dis[100005],n;
vector<int> tu[100005];
void dfs(int x,int father,int d)
{
	int i;
	fa[x][0]=father;
	for(int i=0;i<tu[x].size();i++)
	{
		if(tu[x][i]==father)
			continue;
		dis[tu[x][i]]=d+1;
		dfs(tu[x][i],x,d+1);
	}
}
int LCA(int x,int y)
{
	int i;
	if(dis[x]<dis[y])
		swap(x,y);
	int f=dis[x]-dis[y];
//	cout<<f<<endl;
	for(i=0;(1<<i)<=f;i++)
	{
		if((1<<i)&f)
			x=fa[x][i];
	}
	if(x!=y)
	{
		for(i=(int)log2(n);i>=0;i--)
		{
			if(fa[x][i]!=fa[y][i])
			{
				x=fa[x][i];
				y=fa[y][i];
			}
		}
		x=fa[x][0];
	}
	return x;
}
int lens(int x,int y)
{
	int i=LCA(x,y);
	return dis[x]+dis[y]-2*dis[i]; 
}
int main()
{
	int T,x,y,z,i,j;
	cin>>n>>T;
	for(i=1;i<=n-1;i++)
	{
		cin>>x>>y;
		tu[x].push_back(y);
		tu[y].push_back(x);
	}
	dis[1]=1;
	dfs(1,0,1);
	/*cout<<"----------------------"<<endl;
	for(i=1;i<=n;i++)
	{
		cout<<"dis["<<i<<"]:"<<dis[i]<<endl;
		cout<<"fa["<<i<<"]:"<<fa[i]<<endl;
	}
	cout<<"----------------------"<<endl;*/
	for(j=1;(1<<j)<=n;j++)
	{
		for(i=1;i<=n;i++)
		{
			fa[i][j]=fa[fa[i][j-1]][j-1];
		}
	}
	int h1,h2,h3,res;
	while(T--)
	{
		x=y=z=0;
		cin>>x>>y>>z;
		//int h1,h2,h3,res;
		h1=LCA(x,y);
		h2=LCA(x,z);
		h3=LCA(y,z);
	//	cout<<"h1 h2 h3:"<<h1<<" "<<h2<<" "<<h3<<endl;
		if(h1==h2)
			res=h3;
		else if(h1==h3)
			res=h2;
		else if(h2==h3)
			res=h1;
		int k=lens(x,res)+lens(y,res)+lens(z,res);
		cout<<res<<" "<<k<<endl;
	}
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值