题目
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();
}