[bzoj 1787][Ahoi2008]Meet 紧急集合

题意

给定一棵树(N<=500000),多次询问(M<=500000):找出一个点到给定3个点的距离最小。

思路

先考虑如果是两个点的话,答案显然可以是它们的 lca l c a
可以猜测答案可能跟 3 3 个点的lca有关。

对于树上任意不同的 3 3 个点,可以产生总共3个点对,其中 lca l c a 只有 2 2 种。
证明:
考虑有两个点x,y lca(x,y)=a l c a ( x , y ) = a ,且 a=x a = x ,现在加一个新的 z z ,如果z不属于 a a 的子树,那显然lca(x,z)=lca(y,z),如果 z z 属于a的子树,可以归为下面的情况。

考虑现在有两个点 x x y lca(x,y) l c a ( x , y ) a a ,且ax, ay a ≠ y ,现在我们把另一个点 z z 加进去,对于a来说, z z 可以划分出两种状态,一种是z属于 a a 的子树,另一种不属于。
对于z不属于 a a 的子树时,可以得到lca(y,z)=lca(a,z)=lca(x,z)所以 lca(y,z)=lca(x,z) l c a ( y , z ) = l c a ( x , z )

z z 属于a的子树时,假设把 a a 去掉,出现了许多的树,又有了两种情况,一种是z不属于 x x y所代表子树中,另一种 z z 属于x y y 代表的子树里,第一种情况,lca(z,x)=lca(y,z),对于第二种情况假设 z z 属于x代表的子树里,那么 lca(x,y)=lca(z,y)=a l c a ( x , y ) = l c a ( z , y ) = a

证毕。

答案一定在 2 2 lca之中
证明:
对于任意一个不与两个 lca l c a 重合的点,考虑最好情况下, 3 3 个点到这个点的路径上一定至少有两个点会经过lca,且会经过另外 1 1 个点,答案显然比在lca大。

答案为深度大的 lca l c a
考虑到如果选择深度小的 lca l c a 会使有的点做无用功,则答案一定为深度大的 lca l c a ,即 3 3 lca中出现 1 1 <script type="math/tex" id="MathJax-Element-16572">1</script>的那个。

#include<cstdio>
const int N=500005;
const int M=500005;
const int BIT=19;
struct edge{
    int to,nx;
}e[M<<1];
int h[N],tot,fa[20][N],dep[N],n,m;
void swap(int &x,int &y){
    int t=x;
    x=y;
    y=t;
}
void ins(int x,int y){
    e[++tot]=(edge){y,h[x]};
    h[x]=tot;
}
void lk(int x,int y){
    ins(x,y);
    ins(y,x);
}
void dfs(int x,int y){
    fa[0][x]=y;
    dep[x]=dep[y]+1;
    for(int i=h[x];i;i=e[i].nx){
        if(e[i].to==y)continue;
        dfs(e[i].to,x);
    }
}
void init(){
    for(int i=1;i<=BIT;i++){
        for(int j=1;j<=n;j++){
            fa[i][j]=fa[i-1][fa[i-1][j]];
        }
    }
}
int getlca(int x,int y){
    if(dep[y]>dep[x])swap(x,y);
    int delta=dep[x]-dep[y];
    for(int i=0;i<BIT;i++){
        if(delta&(1<<i)){
            x=fa[i][x];
        }
    }
    if(x==y)return x;
    for(int i=BIT;i>=0;i--){
        if(fa[i][x]!=fa[i][y]){
            x=fa[i][x];
            y=fa[i][y];
        }
    }
    return fa[0][x];
}
int calc(int a,int b,int c,int d){
    int ret=0;
    int t=getlca(a,d);
    ret += dep[d]+dep[a]-2*dep[t];
    t=getlca(b,d);
    ret+=dep[d]+dep[b]-2*dep[t];
    t=getlca(c,d);
    ret+=dep[c]+dep[d]-2*dep[t];
    return ret;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        lk(a,b);
    }
    dfs(1,0);
    init();
    for(int i=1;i<=m;i++){
        int a,b,c,d,e,f;
        scanf("%d%d%d",&a,&b,&c);
        d=getlca(a,b);
        e=getlca(a,c);
        f=getlca(b,c);
        if(d==e){
            printf("%d %d\n",f,calc(a,b,c,f));
        }else if(d==f){
            printf("%d %d\n",e,calc(a,b,c,e));
        }else{
            printf("%d %d\n",d,calc(a,b,c,d));
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值