BZOJ 1787/ 洛谷 4281 [AHOI2008] Meet 紧急集合

版权声明:EL PSY CONGROO ! https://blog.csdn.net/THIS_IS_HPQ/article/details/82981969

题目描述

欢乐岛上有个非常好玩的游戏,叫做“紧急集合”。在岛上分散有N个等待点,有N-1条道路连接着它们,每一条道路都连接某两个等待点,且通过这些道路可以走遍所有的等待点,通过道路从一个点到另一个点要花费一个游戏币。

参加游戏的人三人一组,开始的时候,所有人员均任意分散在各个等待点上(每个点同时允许多个人等待),每个人均带有足够多的游戏币(用于支付使用道路的花费)、地图(标明等待点之间道路连接的情况)以及对话机(用于和同组的成员联系)。当集合号吹响后,每组成员之间迅速联系,了解到自己组所有成员所在的等待点后,迅速在N个等待点中确定一个集结点,组内所有成员将在该集合点集合,集合所用花费最少的组将是游戏的赢家。

小可可和他的朋友邀请你一起参加这个游戏,由你来选择集合点,聪明的你能够完成这个任务,帮助小可可赢得游戏吗?

输入输出格式

输入格式:

第一行两个正整数N和M(N<=500000,M<=500000),之间用一个空格隔开。分别表示等待点的个数(等待点也从1到N进行编号)和获奖所需要完成集合的次数。 随后有N-1行,每行用两个正整数A和B,之间用一个空格隔开,表示编号为A和编号为B的等待点之间有一条路。 接着还有M行,每行用三个正整数表示某次集合前小可可、小可可的朋友以及你所在等待点的编号。

输出格式:

一共有M行,每行两个数P,C,用一个空格隔开。其中第i行表示第i次集合点选择在编号为P的等待点,集合总共的花费是C个游戏币。

输入输出样例

输入样例#1:

6 4  
1 2  
2 3  
2 4 
4 5
5 6
4 5 6
6 3 1
2 4 4 
6 6 6

输出样例#1:

5 2
2 5
4 1
6 0


说明

40%的数据中N<=2000,M<=2000
100%的数据中,N<=500000,M<=500000

很显然这是一道LCA的题目,但是每次要求的点数从2个增加到了3个。

既然要求出最小的花费,那么所选的集合点肯定要使到给出的3个点的距离尽量短,且"重复路径"也要尽量少。

什么是"重复路径"呢?就比如下面这张图。

                                                                 

假如我们要求的是3,4,6这3个点的集合点。显而易见选4是最优的。

经过一点点思考可以发现,所求的集合点必定在这三个点所构成的链上。

而到这条路链上的点的花费的区别就是这个"重复路径"。这3个点走到4是没有重复路径的。

而如果走到2的话4->2这条路径就会被经过两次,这样显然花费就更高了。

然后我们需要求出这个最优的点。还是上面那张图,我直接复制下来方便看吧。

                                                                      

通过直接观察可得,3和4的LCA是2,3和6的LCA也是2,4和6的LCA是4。

更进一步,3个点两两的LCA必定有两个是相同的。且3个点的分布只有两种情况。

1.三个点在同一侧,就比如上图中的4,5,6。这种情况的最优集合点显然是中间那个点。

2.两个点在一侧,剩下一个点在另一侧。上图中的3,4,6。这种情况为了使得重复路径最少,我们所选的点要尽量与那两个在同一侧的点近。当然不能比较浅的点那个点更深了(上图中我们选的最深的点不能比点4更深,不然就会多出一条重复路径)。这样我们选的点就肯定是在同一侧的那两个点的LCA了(上图中就是点4了)。

而确定这个点很简单。之前我说过有两个LCA是必定相同的。单独在一侧的点和另外两个在另一侧的点的LCA就是相同的(LCA(3,4)=LCA(3,6))。我们要选取的就是另一个LCA了。虽然这是情况2的解,但是情况1只是情况2的一种特殊形式,所以就是通解了。

要选的点求出来了那么就只剩下计算花费了。比较简单的方法就是对这3个点和所选的集合点的深度做差再取绝对值最后加起来,这样是很麻烦的。

                                                   

假设给出的点分别是a,b,c。k1&k2是LCA(a,b)和LCA(a,c)。k3是LCA(b,c)。我们记录了每个点的深度Depth[i]。根据我上面的分析,我们所选的集合点为k3。三个点到k3的距离为Depth[a]+Depth[b]+Depth[c]-3*Depth[k3]+2*Depth[k3]-Depth[k1]-Depth[k2]。化简一下就是Depth[a]+Depth[b]+Depth[c]-Depth[k1]-Depth[k2]-Depth[k3]。

下面贴上代码,我用倍增写的LCA。如果代码炸了我这里还放了一份

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#define MAXN 500005
using namespace std;
struct edge
{
    int next,node;
}h[MAXN*2];//邻接表存图
int n,q,tot=0;
int Head[MAXN],Depth[MAXN],p[MAXN][32];//Head是上面存图里的头边。Depth存每个点的深度
inline int read()//p[i][j]表示点i向上跳2^j到的点
{
    int sum=0,t=1;char ch;ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-') t=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
    return sum*t;
}//快读
inline void add(int u,int v)
{
    h[++tot].next=Head[u];
    h[tot].node=v;
    Head[u]=tot;
}//加边
inline void dfs(int pos)
{
    for(register int i=Head[pos];i;i=h[i].next)
    {
        int v=h[i].node;
        if(p[pos][0]!=v)
        {
            p[v][0]=pos;
            Depth[v]=Depth[pos]+1;
            dfs(v);
        }
    }
}//搜索跑一遍收集图的信息
inline int lca(int a,int b)
{
    if(Depth[a]>Depth[b]) swap(a,b);
    int delta=Depth[b]-Depth[a];
    for(register int i=0;(1<<i)<=delta;++i)
        if((1<<i)&delta)
            b=p[b][i];
    if(a!=b)
    {
        for(register int i=(int)log2(n);i>=0;--i)
            if(p[a][i]!=p[b][i])
            {
                a=p[a][i];
                b=p[b][i];
            }
        a=p[a][0];
    }
    return a;
}//倍增求LCA
int main()
{
    int x,y,z;
    n=read();
    q=read();
    for(register int i=1;i<n;++i)
    {
        x=read();
        y=read();
        add(x,y);
        add(y,x);
    }
    for(register int i=1;i<=n;++i)
        if(!p[i][0])
        {
            Depth[i]=1;
            p[i][0]=i;
            dfs(i);
        }//默认点1为根节点。这样写防止图不联通。
    for(register int j=1;(1<<j)<=n;++j)
        for(register int i=1;i<=n;++i)
            p[i][j]=p[p[i][j-1]][j-1];//处理倍增。
    for(register int i=1;i<=q;++i)
    {
        int ans;
        x=read();
        y=read();
        z=read();
        int k1=lca(x,y),k2=lca(x,z),k3=lca(y,z);
        if(k1==k2) ans=k3;
        else if(k2==k3) ans=k1;
        else if(k1==k3) ans=k2;
        printf("%d %d\n",ans,Depth[x]+Depth[y]+Depth[z]-Depth[k1]-Depth[k2]-Depth[k3]);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/THIS_IS_HPQ/article/details/82981969