P4281 [AHOI2008]紧急集合 / 聚会

P4281 [AHOI2008]紧急集合 / 聚会

给定一棵树, 边权为 \(1\), 多次询问, 给出三个点 \(a, b, c\) 求三个点的中点和到中点的总距离
定义中点为 \(x\) 满足 \(dis(a, x) + dis(b, x) + dis(c, x)\) 最小


调试日志: \(jump\) 数组开小, 只开了 \(19\) ...


Solution

首先是树上两点距离:\[dis(x, y) = dep[x] + dep[y] - 2 * dep[LCA(x, y)]\]
根到 \(LCA\) 的距离计算了两遍, 减去, 即可得到上式

回到这题, 发现三点两两 \(LCA\) 中总有两个一样
为什么呢? 设 \(x = LCA(a, b)\)\(LCA(x, c)\) 即为重合的点, 因为此点是 \(x\) 的祖先, 必然是 \(a, b\) 的祖先
然后 找规律 可得集合点一定是三个 \(LCA\) 中深度最大的那个
我呸
可以发现, 深度大的那个 \(LCA\) 掌管着更深的两个点, 用反证法, 若是将集合点上移 \(d\) 对于一个点 \(-d\) 对于另外深度大两个点一共 \(+2d\) , 即总距离 \(+d\) , 不优
而若是将集合点下移 \(d\) , 那么此点将不在三点的公共路线上, 必然不优
综上可得: 集合点在三个 \(LCA\) 中深度大的点

然后套距离公式, 发现化简得如下式子\[\min dis(a, b, c) = dep[a] + dep[b] + dep[c] - dep[lca1] - dep[lca2] - dep[lca3]\]

Code

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
int RD(){
    int out = 0,flag = 1;char c = getchar();
    while(c < '0' || c >'9'){if(c == '-')flag = -1;c = getchar();}
    while(c >= '0' && c <= '9'){out = out * 10 + c - '0';c = getchar();}
    return flag * out;
    }
const int maxn = 1000019,INF = 1e9 + 19;
int head[maxn],nume = 1;
struct Node{
    int v,dis,nxt;
    }E[maxn << 3];
void add(int u,int v,int dis){
    E[++nume].nxt = head[u];
    E[nume].v = v;
    E[nume].dis = dis;
    head[u] = nume;
    }
int num, na;
int dep[maxn], jump[maxn][25];
void dfs(int id, int F){//处理出深度用于计算距离
    for(int i = head[id];i;i = E[i].nxt){
        int v = E[i].v;
        if(v == F)continue;
        dep[v] = dep[id] + 1;
        jump[v][0] = id;
        dfs(v, id);
        }
    }
void get_jump(){
    for(int i = 1;i <= 19;i++){
        for(int j = 1;j <= num;j++){
            jump[j][i] = jump[jump[j][i - 1]][i - 1];
            }
        }
    }
int LCA(int x, int y){
    if(dep[x] < dep[y])swap(x, y);
    for(int i = 19;i >= 0;i--)if(dep[jump[x][i]] >= dep[y])x = jump[x][i];
    if(x == y)return x;
    for(int i = 19;i >= 0;i--){
        if(jump[x][i] != jump[y][i])x = jump[x][i], y = jump[y][i];
        }
    return jump[x][0];
    }
int main(){
    num = RD(), na = RD();
    for(int i = 1;i <= num - 1;i++){
        int u = RD(), v = RD();
        add(u, v, 1), add(v, u, 1);
        }
    dep[1] = 1;
    dfs(1, -1), get_jump();
    while(na--){
        int a = RD(), b = RD(), c = RD();
        int lca1 = LCA(a, b), lca2 = LCA(a, c), lca3 = LCA(b, c);
        int ans = dep[lca1] > dep[lca2] ? lca1 : lca2;
        ans = dep[lca3] > dep[ans] ? lca3 : ans;
        int dis = dep[a] + dep[b] + dep[c] - dep[lca1] - dep[lca2] - dep[lca3];
        printf("%d %d\n", ans, dis);
        }
    return 0;
    }

转载于:https://www.cnblogs.com/Tony-Double-Sky/p/9720860.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值