lca的应用 P4281 [AHOI2008] 紧急集合 / 聚会

传送门 : P4281 [AHOI2008] 紧急集合 / 聚会
题目大意
给出一棵树,以及给出m个询问:每个询问包括a,b,c三个数,在树上找一个点使得三点到该点的距离和最小。
我的做法是
由于在树上求两点之间的距离会用到lca,所以就往这个方面想了一下。
发现这个点要在a,b,c之间才行,更具体的这个点是它们两两之间的lca的深度最大的那一个。
所以就直接敲了个很朴素的计算,结果tle了,如下

        int sum = 0;
        sum += dep[a] + dep[l] - 2*dep[lca(a,l)];
        sum += dep[b] + dep[l] - 2*dep[lca(b,l)];
        sum += dep[c] + dep[l] - 2*dep[lca(c,l)];

后面想怎么样简化这个计算才慢慢发现了问题的本质:
如果这个点不在我要求的这个点上面的话,通过画一下路径发现它是会走多一段的,而在我要找到这个点上面的时候,刚好是等于他们两两之间的距离的和的一半。
也就是 sum = dep[a] + dep[b] + dep[c] - dep[l1] - dep[l2] - dep[l3];

#include<bits/stdc++.h>
using namespace std;
const int N = 5e5+100;
int n,m;
vector<int>e[N];
int f[N][20],dep[N];
void dfs(int u,int fa)
{
    f[u][0] = fa;
    dep[u] = dep[fa]+1;
    for(int i = 1 ; i < 20 ; ++i)
        f[u][i] = f[f[u][i-1]][i-1];
    for(int v : e[u])
    {
        if(v == fa)continue;
        dfs(v,u);
    }
}
int lca(int u,int v)
{
    if(dep[u] < dep[v])swap(u,v);
    for(int i = 19 ; ~i ; --i)
        if(dep[f[u][i]] >= dep[v])
            u = f[u][i];
    if(u == v)return v;
    for(int i = 19 ; ~i ; --i)
        if(f[u][i] != f[v][i])u = f[u][i],v = f[v][i];
    return f[u][0];
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin>>n>>m;
    for(int i = 1 ; i < n ; ++i)
    {
        int u,v;
        cin>>u>>v;
        e[u].push_back(v);e[v].push_back(u);
    }
    dfs(1,0);
    while(m--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        int l1 = lca(a,b),l2 = lca(b,c),l3 = lca(a,c);
        int l = 0;
        if(dep[l1] > dep[l])l = l1;
        if(dep[l2] > dep[l])l = l2;
        if(dep[l3] > dep[l])l = l3;
        int sum = 0;
        sum = dep[a] + dep[b] + dep[c] - dep[l1] - dep[l2] - dep[l3];
        cout<<l<<" "<<sum<<endl;
    }
    return 0;
}
posted on 2025-08-20 00:40  钟一一  阅读(15)  评论(0)    收藏  举报