P4281 [AHOI2008] 紧急集合 / 聚会 LCA

解题思路

这道题目要求我们在树结构中找到三个节点的最佳集合点,使得三人到达该点的总距离最短。关键点在于:

  1. 树的性质:n个节点n-1条边的连通无向图

  2. LCA应用:利用最近公共祖先来高效计算节点间距离

  3. 集合点选择:三个节点的最佳集合点必定是某两个节点的LCA

#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;

// f[i][j]表示节点i的2^j级祖先
int f[N][26];  
// dep[i]表示节点i的深度(根节点深度为1)
int dep[N];    
int n,m;
// 邻接表存储树结构
vector<int> g[N];  

// DFS预处理LCA信息
void dfs(int x,int fa)
{
    dep[x] = dep[fa] + 1;  // 计算当前节点深度
    f[x][0] = fa;          // 直接父节点
    
    // 预处理倍增表(2^i级祖先)
    for(int i = 1; i <= 20; i++)
    {
        int y = f[x][i - 1];
        f[x][i] = f[y][i - 1];  // 2^i = 2^(i-1) + 2^(i-1)
    }
    
    // 递归处理子节点
    for(int i = 0; i < g[x].size(); i++)
    {
        int y = g[x][i];
        if(y != fa)
            dfs(y,x);
    }
}

// LCA查询函数
int lca(int x,int y)
{
    // 确保x是较深的节点
    if(dep[x] < dep[y]) swap(x,y);
    
    // 将x调整到与y同一深度
    for(int i = 20; i >= 0; i--){
        if(dep[x] - (1 << i) >= dep[y])
            x = f[x][i];
    }
    
    // 如果此时已经相同,直接返回
    if(x == y) return x;
    
    // 同时向上跳跃查找
    for(int i = 20; i >= 0; i--){
        if(f[x][i] != f[y][i]){  // 祖先不同才跳跃
            x = f[x][i];
            y = f[y][i];
        }
    }
    return f[x][0];  // 返回最终的LCA
}

// 计算两点间距离
int dist(int x,int y)
{
    return dep[x] + dep[y] - 2 * dep[lca(x,y)];
}

int main()
{
    // 输入树结构
    cin >> n >> m;
    for(int i = 1; i < n; i++)
    {
        int x,y;
        cin >> x >> y;
        g[x].push_back(y);
        g[y].push_back(x);
    }
    
    // 预处理LCA信息(以1为根节点)
    dfs(1,0);
    
    // 处理每个查询
    while(m--)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        
        // 计算三个两两LCA
        int t1 = lca(x,y), t2 = lca(x,z), t3 = lca(y,z);
        
        // 确定最佳集合点(三个LCA中不同的那个)
        int pos;
        if(t1 == t2) pos = t3;
        else if(t1 == t3) pos = t2;
        else pos = t1;
        
        // 计算总距离
        int ans = dist(x,pos) + dist(y,pos) + dist(z,pos);
        printf("%d %d\n",pos,ans);
    }
    return 0;
}

1. 三个LCA的层次关系证明

设三个节点为x、y、z,定义:

  • t1 = lca(x,y)

  • t2 = lca(x,z)

  • t3 = lca(y,z)

关键性质:这三个LCA中,必定有两个相同,且第三个要么:

  • 与它们相同(当三个LCA都相同时)

  • 是它们的后代(即深度更大)

为什么?

因为树的层次结构决定了:

  • 如果考虑x、y、z的所有可能位置关系,它们的共同祖先链会形成一个"分叉结构"

  • 假设t1和t2相同(=A),那么:

    • A是x和y的祖先

    • A也是x和z的祖先

    • 那么y和z的LCA t3只能是:

      • A本身(如果y和z在A的同一子树)

      • 或者A的某个后代(如果y和z在A的不同子树)

2. 为什么不同的那个更深?

考虑t1=t2=A,而t3≠A的情况:

  • t3必须是A的后代,因为:

    • t3是y和z的LCA

    • y和z都在A的子树下(因为A是它们的祖先)

    • 如果y和z在A的同一直接子树,那么t3=A

    • 只有当y和z在A的不同子树时,t3才会是A的某个后代(更深)

3. 实例验证

考虑这个树结构:

       R
     / | \
    A  B  C
   / \    |
  x   y   z

t1 = lca(x,y) = A

t2 = lca(x,z) = R

t3 = lca(y,z) = R
这里t2=t3=R,t1=A不同,且A比R更深

4. 数学归纳

用归纳法可以证明:

  • 对于任何三个节点,它们的LCA集合中:

    • 要么三个都相同(当它们在一条链上)

    • 要么两个相同,第三个是它们的后代(更深)

5. 为什么选择最深的?

因为:

  • 更深的LCA意味着更靠近实际节点

  • 选择更深的点可以减少多个节点需要向上走的距离

  • 虽然一个节点可能需要向下走,但总体上节省了更多路径

6. 反证法

假设"不同的那个"不是最深的:

  • 那么存在另一个LCA比它更深

  • 但是根据性质,其他两个LCA相同且比它浅

  • 这与树的层次结构矛盾(不能有两个不同的LCA都比第三个深)

结论

这个性质之所以成立,完全是由树的层次结构和LCA的定义决定的。选择"不同的那个LCA"实际上就是在选择三个节点的"最深层汇聚点",这正是最小化总距离的关键所在。

posted @ 2025-04-29 18:54  CRt0729  阅读(39)  评论(0)    收藏  举报