• BZOJ1832: [AHOI2008]聚会


    BZOJ1832: [AHOI2008]聚会

    Description

    Y岛风景美丽宜人,气候温和,物产丰富。Y岛上有N个城市,有N-1条城市间的道路连接着它们。每一条道路都连接某两个城市。幸运的是,小可可通过这些道路可以走遍Y岛的所有城市。神奇的是,乘车经过每条道路所需要的费用都是一样的。小可可,小卡卡和小YY经常想聚会,每次聚会,他们都会选择一个城市,使得3个人到达这个城市的总费用最小。 由于他们计划中还会有很多次聚会,每次都选择一个地点是很烦人的事情,所以他们决定把这件事情交给你来完成。他们会提供给你地图以及若干次聚会前他们所处的位置,希望你为他们的每一次聚会选择一个合适的地点。

    Input

    第一行两个正整数,N和M。分别表示城市个数和聚会次数。

    后面有N-1行,每行用两个正整数A和B表示编号为A和编号为B的城市之间有一条路。

    城市的编号是从1到N的。再后面有M行,每行用三个正整数表示一次聚会的情况:小可可所在的城市编号,小卡卡所在的城市编号以及小YY所在的城市编号。

    Output

    一共有M行,每行两个数Pos和Cost,用一个空格隔开。

    表示第i次聚会的地点选择在编号为Pos的城市,总共的费用是经过Cost条道路所花费的费用。

    Sample Input

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

    Sample Output

    5 2
    2 5
    4 1
    6 0

    数据范围:
    100%的数据中,N<=500000,M<=500000。
    40%的数据中N<=2000,M<=2000。

    题解Here!

    首先,应该能看出来和$LCA$跑不了关系。。。
    然后开始简化问题:
    如果是两个点,那么好办,直接$LCA$即可。
    然后推到三个点:
    我们发现我们要求一个点,使得这个点到给定的三个点的距离之和最小。
    有一个结论:这个点一定在给定三个点的互达的路径上。
    还有个结论:给定的三个点的两两的$LCA$中,一定至少有两个是相同的,并且是深度最小的那个。
    这两个结论相信有基础的$OIer$们都能画几个图然后自行$YY$出来。。。
    然后考虑$LCA$对路径的影响:
    设$LCA(a,b)=x$,且$x$是深度最大的那个$LCA$。
    那么$dis(x,a)+dis(x,b)$一定是最小的。
    并且根据上面的结论,如果设$LCA(a,c)=LCA(b,c)=y$,那么$y$的深度一定比$x$小。
    并且$dis(x,a)+dis(x,b)+dis(x,c)$一定是最小的。
    证明?如下:
    假设最优的点为$p$,那么有两种情况:
    • $p$的深度比$x$小:

    相当于$dis(p,a)-dis(x,a)=dis(p,b)-dis(x,b)=dis(x,c)-dis(p,c)=w$。

    一共增加了$w$,当然不是最优的。

    • $p$的深度比$x$大:

    相当于$dis(x,a)-dis(p,a)=dis(p,b)-dis(x,b)=dis(p,c)-dis(x,c)=w$。

    即:对于$a$,距离减少了$w$,但是对于$b,c$来说,距离都增加了$w$。

    也是一共增加了$w$,当然也不是最优的。

    到此,我们找到了到$a,b,c$三个点距离最少的点:

    三个$LCA$中,深度最大的那个。

    于是距离之和就好办了:

    我们知道:

    $$dis(u,v)=deep[u]+deep[v]-2 imes deep[LCA(u,v)]$$

    而:

    $$dis=dis(x,a)+dis(x,b)+dis(x,c)$$

    于是:

    $$dis=deep[x]+deep[a]-2 imes deep[LCA(x,a)]+deep[x]+deep[b]-2 imes deep[LCA(x,b)]+deep[x]+deep[c]-2 imes deep[LCA(x,c)]$$

    把那些$LCA$换掉:

    $$dis=3 imes deep[x]+deep[a]+deep[b]+deep[c]-2 imes(deep[x]+deep[x]+deep[y])$$

    展开,合并同类项:

    $$dis=deep[a]+deep[b]+deep[c]-deep[x]-2 imes deep[y]$$

    哇!这不就是:

    $$dis=deep[a]+deep[b]+deep[c]-deep[LCA(a,b)]-deep[LCA(b,c)]-deep[LCA(a,c)]$$

    好河蟹啊有木有!

    于是这题只要再写个树链剖分求$LCA$即可。

    现在就怕$NOIP$考这种思维题。。。

    附代码:

    #include<iostream>
    #include<algorithm>
    #include<cstdio>
    #define MAXN 500010 
    using namespace std;
    int n,m,c=1;
    int head[MAXN],deep[MAXN],son[MAXN],size[MAXN],fa[MAXN],top[MAXN];
    struct Edge{
    	int next,to;
    }a[MAXN<<1];
    inline int read(){
    	int date=0,w=1;char c=0;
    	while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
    	while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();}
    	return date*w;
    }
    inline void add(int x,int y){
    	a[c].to=y;a[c].next=head[x];head[x]=c++;
    	a[c].to=x;a[c].next=head[y];head[y]=c++;
    }
    void dfs1(int rt){
    	son[rt]=0;size[rt]=1;
    	for(int i=head[rt];i;i=a[i].next){
    		int will=a[i].to;
    		if(!deep[will]){
    			deep[will]=deep[rt]+1;
    			fa[will]=rt;
    			dfs1(will);
    			size[rt]+=size[will];
    			if(size[son[rt]]<size[will])son[rt]=will;
    		}
    	}
    }
    void dfs2(int rt,int f){
    	top[rt]=f;
    	if(son[rt])dfs2(son[rt],f);
    	for(int i=head[rt];i;i=a[i].next){
    		int will=a[i].to;
    		if(will!=fa[rt]&&will!=son[rt])dfs2(will,will);
    	}
    }
    int LCA(int x,int y){
    	while(top[x]!=top[y]){
    		if(deep[top[x]]<deep[top[y]])swap(x,y);
    		x=fa[top[x]];
    	}
    	if(deep[x]>deep[y])swap(x,y);
    	return x;
    }
    void work(){
    	int x,y,z,lca1,lca2,lca3,ans,dis;
    	while(m--){
    		x=read();y=read();z=read();
    		lca1=LCA(x,y);lca2=LCA(y,z);lca3=LCA(x,z);
    		dis=0;
    		if(lca1==lca2)ans=lca3;
    		else if(lca1==lca3)ans=lca2;
    		else ans=lca1;
    		dis=deep[x]+deep[y]+deep[z]-deep[lca1]-deep[lca2]-deep[lca3];
    		printf("%d %d
    ",ans,dis); 
    	}
    }
    void init(){
    	int x,y;
    	n=read();m=read();
    	for(int i=1;i<n;i++){
    		x=read();y=read();
    		add(x,y);
    	}
    	deep[1]=1;
    	dfs1(1);
    	dfs2(1,1);
    }
    int main(){
    	init();
    	work();
    	return 0;
    }
    
  • 相关阅读:
    获取具体地址的经纬度
    git更换 拉取推送地址
    array_merge 优化调整
    重启电脑后,redis 6380端口关闭重启
    清空git默认的用户名和密码,
    对一个给定的二维数组按照指定的键值进行排序
    Vim编辑器-批量注释与反注释
    Linux信号处理
    Linux 进程间通信
    mkdir
  • 原文地址:https://www.cnblogs.com/Yangrui-Blog/p/9905622.html
Copyright © 2020-2023  润新知