聚会「AHOI 2008」

题意

有一颗树,多次询问:给出三个节点,求节点\(k\)使距离之和最小,且求距离。

思路

\(lca\)裸题。

这里可以证明一个性质:两两\(lca\)会得出三个节点,其中至少有两个重合。

证明:

显然有三种分布情况:

  • 三个节点都在同一颗子树上,这时公共\(lca\)显然为子树根。

  • 两个节点在同一颗子树上。假设这两个点(\(a,b\))的\(lca\)\(x\),而另一个节点为\(y\),那么\(lca(a,y)=lca(b,y)=lca(x,y)\)。显然有两个重合。

  • 三个节点都在不同子树上,同第一种情况。

\(Q.E.D\)

有了上面的性质,我们在计算的时候可以稍微减少计算量,

\(ans=dep[a]+dep[b]+dep[c]-dep[lca(a,b)]+dep[lca(b,c)]+dep[lca(c,a)]\)

但是这样还是过不了,因为\(OJ\)卡倍增\(lca\),所以必须使用树剖求\(lca\)


代码

(倍增90分)

#include <bits/stdc++.h>

using namespace std;

namespace StandardIO {

    template<typename T> inline void read (T &x) {
        x=0;T f=1;char c=getchar();
        for (; c<'0'||c>'9'; c=getchar()) if (c=='-') f=-1;
        for (; c>='0'&&c<='9'; c=getchar()) x=x*10+c-'0';
        x*=f;
    }
    template<typename T> inline void write (T x) {
        if (x<0) putchar('-'),x=-x;
        if (x>=10) write(x/10);
        putchar((x%10)+'0');
    }

}

using namespace StandardIO;

namespace Solve {
    
    const int N=500500;
    
    int n,m;
    int cnt;
    int head[N];
    struct node {
        int to,next;
    } edge[N<<1];
    int fa[N][31],dep[N];
    
    inline void add (int a,int b) {
        edge[++cnt].to=b,edge[cnt].next=head[a],head[a]=cnt;
    }
    void dfs (int now,int f) {
        fa[now][0]=f,dep[now]=dep[f]+1;
        for (register int i=1; (1<<i)<=dep[now]; ++i) {
            fa[now][i]=fa[fa[now][i-1]][i-1];
        }
        for (register int i=head[now]; i; i=edge[i].next) {
            int to=edge[i].to;
            if (dep[to]) continue;
            dfs(to,now);
        }
    }
    inline int lca (int x,int y) {
        if (dep[x]>dep[y]) swap(x,y);
        for (register int i=20; i>=0; --i) {
            if (dep[y]>=dep[x]+(1<<i)) y=fa[y][i];
        }
        if (x==y) return x;
        for (register int i=20; i>=0; --i) {
            if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
        }
        return fa[x][0];
    }

    inline void Main () {
        read(n),read(m);
        for (register int i=1; i<=n-1; ++i) {
            int a,b;
            read(a),read(b);
            add(a,b),add(b,a);
        }
        dfs(1,0);
        for (register int i=1; i<=m; ++i) {
            int a,b,c,ab,bc,ca,ans,dis;
            read(a),read(b),read(c);
            ab=lca(a,b),bc=lca(b,c),ca=lca(c,a);
            if (ab==bc) ans=ca;
            else if (ab==ca) ans=bc;
            else if (bc==ca) ans=ab;
            dis=dep[a]+dep[b]+dep[c]-dep[ab]-dep[bc]-dep[ca];
            write(ans),putchar(' '),write(dis),putchar('\n');
        }
    }

}

int main () {
    Solve::Main();
}

转载于:https://www.cnblogs.com/ilverene/p/11206214.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值