AHOI2008 紧急集合 树上倍增

AHOI2008 紧急集合

题目传送

sol:

如果只有两个点,那么显然目的地就是在他们二者路径上的任意一点。

现在有三个点,考虑两两的路径和lca,发现肯定有两对求得的lca相同,另外一对的lca深度比那两对的lca深度大。

这个深度大一些的那个lca就是目的地(最近点),最小距离就是三者两两距离的二分之一。

a

所以直接树上倍增即可。

#include<bits/stdc++.h>
#define IL inline
#define RG register
#define DB double
#define LL long long
using namespace std;

IL int gi() {
    RG int x=0,p=1; RG char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') p=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') x=x*10+(ch^48),ch=getchar();
    return x*p;
}

const int N=5e5+1;

int n,m,tot,len,npos,dep[N],head[N],f[N][20];

struct LCA{int pos,dis;}s[5];
struct EDGE{int next,to;}e[N<<1];

IL void make(int a,int b) {
    e[++tot]=(EDGE){head[a],b},head[a]=tot;
    e[++tot]=(EDGE){head[b],a},head[b]=tot;
}

void dfs(int x,int fx) {
    RG int i,y;
    for(i=head[x];i;i=e[i].next)
        if((y=e[i].to)!=fx)
            dep[y]=dep[x]+1,f[y][0]=x,dfs(y,x);
}

IL void multiply() {
    RG int i,j;
    for(i=1;i<20;++i)
        for(j=1;j<=n;++j)
            f[j][i]=f[f[j][i-1]][i-1];
}

IL LCA lca(int x,int y) {
    RG int i,dis=0;
    if(dep[x]<dep[y]) swap(x,y);
    for(i=19;i>=0;--i)
        if(dep[f[x][i]]>=dep[y]) dis+=1<<i,x=f[x][i];
    if(x==y) return (LCA){x,dis};
    for(i=19;i>=0;--i)
        if(f[x][i]!=f[y][i])
            dis+=1<<i+1,x=f[x][i],y=f[y][i];
    return (LCA){f[x][0],dis+2};
}

int main()
{
    RG int i,x,y,z;
    n=gi(),m=gi();
    for(i=1;i<n;++i) x=gi(),y=gi(),make(x,y);
    dep[1]=1,dfs(1,1),multiply();
    while(m--) {
        x=gi(),y=gi(),z=gi();
        s[1]=lca(x,y),s[2]=lca(y,z),s[3]=lca(x,z);
        len=(s[1].dis+s[2].dis+s[3].dis)/2;
        for(i=1,npos=0;i<=3;++i)
            if(dep[s[i].pos]>dep[npos]) npos=s[i].pos;
        printf("%d %d\n",npos,len);
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/Bhllx/p/11253269.html