#10136. 「一本通 4.4 练习 3」聚会(找到一个点到树上三点距离总和最小(lca的应用))

libreoj10136
题解:首先,如果每次询问都只有两个点,这个问题就很简单,只要是树上的路径上的点就可以,寻找树上的路径其实就是寻找LCA的过程。这可以启发我们对于三个点的情况的思考,如果这里有三个点,我们来认真的思考一下。经过上一问的启发,我们来思考一下能不能运用LCA来解决这道题。我们可以发现,树上三个点的三对LCA一定有两个是相同的。这是一件想想的话比较显然的事情。必然能够找到某个节点,让三个点中的两个在一侧,一个在另一侧。而这个点就是两个公共的LCA。思考的再深入些(并且结合瞎蒙),我们会发现这个相同的LCA肯定是深度最小的一个LCA。
这里,我们首先可以显而易见的发现,这个点必须在三个点互相通达的路径上。
我们再思考一下LCALCA与路径和的关系。假设我们知道aa和bb的LCALCA是xx,而且xx是上述的3个LCALCA中深度最大的那个,那么可以发现从xx到aa的距离加上从xx到bb的距离一定是最小的。根据上面的结论,我们知道aa,cc和bb,cc的LCALCA点yy一定在一个点上,而且这个yy一定比xx深度小。
那么这个时候,我们会发现此时a,b,c到x的距离和是最小的。证明的话可以这么想:如果x 比x’高,那么虽然c到x的距离减小了w,但是a,b到x’ 的距离均增大了w,显然距离和增大。如果x ′比x低,有一个节点到x ′ 的距离减小了w,剩下两个节点到x’的距离均增大了w,显然距离和也增大。所以我们就找到了到三个点距离和最小的点:这三个点的三对LCALCA中,深度大的那两个LCA就是答案。
我们在求LCA之前,可以先预处理出深度dep,那么从节点u到v的路径长度就是dis = dep[u] + dep[v] - 2*dep[lca(u,v)]。运用这个式子分别算出a,b,c到a1,b1,c1(三个LCA)的距离,最后发现总的dis居然是轮换式:ans = dep[a]+dep[b]+dep[c]-dep[a1]-dep[b1]-dep[c1], 所以就不用分类讨论了。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn=5e5+10;
int n,m;
vector<int> G[maxn];
int p[25];
int f[maxn][25];
int dep[maxn];
inline int read()
{
	int X=0; bool flag=1; char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
	while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
	if(flag) return X;
	return ~(X-1);
}
void init()
{
    p[0]=1;
    for(int i=1;i<=24;i++){
        p[i]=(p[i-1]<<1);
    }
}
void dfs(int u,int fa)
{
    //printf("ko\n");
     dep[u]=dep[fa]+1;
     int len=G[u].size();
     f[u][0]=fa;
     for(int i=1;i<=24;i++){
        f[u][i]=f[f[u][i-1]][i-1];
     }
     for(int i=0;i<len;i++){
        int to=G[u][i];
        if(to==fa)continue;
        dfs(to,u);
     }

}
int lca(int x,int y)
{
   // printf("pppp\n");
    //printf("%d %d\n",dep[x],dep[y]);
    if(dep[x]<dep[y]){
        swap(x,y);
    }
    //printf("pk\n");
    int dx=dep[x]-dep[y];
    for(int i=24;i>=0;i--){
        if(dx>=p[i]){
            dx-=p[i];
            x=f[x][i];
        }
      //  printf("ko\n");
    }
    if(x==y)return x;
    for(int i=24;i>=0;i--){
        if(dep[x]>=p[i]&&f[x][i]!=f[y][i]){
            x=f[x][i];y=f[y][i];
        }
    }
    return f[x][0];
}
int dis(int x,int y)
{
    return dep[x]+dep[y]-(2*dep[lca(x,y)]);
}
int main()
{
    n=read();m=read();
    int u,v;
    for(int i=1;i<=n-1;i++){
        u=read();v=read();
        G[u].push_back(v);G[v].push_back(u);
    }
    init();
    dfs(1,0);
    int x,y,z;
    while(m--){
       // int x,y,z;
        x=read();y=read();z=read();
        int a=lca(x,y),b=lca(y,z),c=lca(z,x);
        int d=0;
        int ans;
        if(a==b){
            ans=c;
        }
        else
        if(b==c){
            ans=a;
        }
        else
        if(c==a){
            ans=b;
        }
        d=dep[x]+dep[y]+dep[z]-dep[a]-dep[b]-dep[c];
        printf("%d %d\n",ans,d);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值