Bzoj1787-P4281 [AHOI2008]紧急集合 / 聚会

时间:2021-08-09
本文章向大家介绍Bzoj1787-P4281 [AHOI2008]紧急集合 / 聚会,主要包括Bzoj1787-P4281 [AHOI2008]紧急集合 / 聚会使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

十分感谢 \(\texttt{darkbzoj}\) 所提供的的数据。

没有这个数据我估计调不出来。

description

描述
欢乐岛上有个非常好玩的游戏,叫做“紧急集合”。在岛上分散有 n 个等待点,有 n−1 条道路连接着它们,每一条道路都连接某两个等待点,且通过这些道路可以走遍所有的等待点,通过道路从一个点到另一个点要花费一个游戏币。
参加游戏的人三人一组,开始的时候,所有人员均任意分散在各个等待点上(每个点同时允许多个人等待),每个人均带有足够多的游戏币(用于支付使用道路的花费)、地图(标明等待点之间道路连接的情况)以及对话机(用于和同组的成员联系)。当集合号吹响后,每组成员之间迅速联系,了解到自己组所有成员所在的等待点后,迅速在 n 个等待点中确定一个集结点,组内所有成员将在该集合点集合,集合所用花费最少的组将是游戏的赢家。
小可可和他的朋友邀请你一起参加这个游戏,由你来选择集合点,聪明的你能够完成这个任务,帮助小可可赢得游戏吗?
输入格式
第一行两个正整数 n 和 m,分别表示等待点的个数(等待点也从 11 到 nn 进行编号)和获奖所需要完成集合的次数。
随后 n-1n−1 行,每行两个正整数 a,b表示编号为 a 和编号为 b 的等待点之间有一条路。
随后 m 行,每行用三个正整数 x,y,z表示某次集合前小可可、小可可的朋友以及你所在等待点的编号。
输出格式
输出共 m 行,每行两个用空格隔开的整数 p,c。其中第 i 行表示第 i 次集合点选择在编号为 p 的等待点,集合总共的花费是 c 个游戏币。
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
Output
5 2
2 5
4 1
6 0

Hint
1<=x,y,z,n,m<=5e5

solution

说实话这个样例给的真的很有迷惑性。

这个数据(↑)让我以为 \(lca(x,lca(y,z))\) 就是 \(p\)

(当然也因为我没认真读题)

后面手玩了一组数据:

然后回去看样例发现,诶不对啊。

为什么 query_p(4,5,6)=5

然后发现这个是在中间的.

于是我觉得要看三个 \(\texttt{LCA}\) 里面 \(depth\) 最大的那一个。

为什么?

如图,绿边是真正的树边。

很明显我们不是去 \(lca(x,y)\) 集合就是去 \(lca(x,z) \operatorname{or} \,lca(y,z)\)

那么边 \(\,alpha\, beta\, ceta\) 边都是必须走的。

相当于先让 \(x,y\) 跳到 \(lca(x,y)\) ,让 \(z\) 跳到最上面那个公共的 \(\texttt{LCA}\)

但如果让 \(x,y\) 同时去最上面的话,\(c\) 会加上 \(2\times delta\)

如果只让 \(z\)\(lca(x,y)\) 的话只会加上 \(delta\)

明显更优,所以集合点 \(p\) 是深度更大的 \(\texttt{LCA}\)

那么就把 \(x,y,z\)\(depth\) 分别和这个 \(depth\) 最大的的 \(\texttt{LCA}\)\(depth\) 分别作差然后求和。

(相等就特判)

于是又挂了 。

画了下面这个图之后我分析了一下:

假设查询是这样子的

很明显 \(depth\) 最大的那个 \(\texttt{LCA}\)\(y\)

但是 \(depth[z]=depth[y]\) ,而实际上 \(z\) 还要先到 \(root\) 再到 \(y\)去,。

所以深度相等的时候直接减会错。

要稍微改写一下式子。

那么设 \(depth\) 更大的 \(\texttt{LCA}\)\(p\),

\(lca(x,z)=rxz,lca(y,z)=ryz,lca(x,y)=rxy(p)\)

\(c=c_x+c_y+c_z\)

\(c_z=dep[z]-dep[root]+dep[p]-dep[root]\)

\(c_x=dep[x]-dep[p]\)

\(c_y=dep[y]-dep[p]\)

那么

\(c=dep[z]-dep[root]+dep[p]-dep[root]+dep[x]-dep[p]+dep[y]-dep[p]\)

\(=dep[x]+dep[z]+dep[y]-dep[p]-2\times dep[root]\)

所以

\(c=dep[x]+dep[y]+dep[z]-dep[p]-dep[ryz]-dep[rxz]\)

\(c=\sum_{i \in \{x,y,z\}} dep[i]-\sum_{j \in \{rxy,rxz,ryz\}} dep[j]\)

因为 \(x,y,z\) 的顺序不影响式子(只需要在code里判一下),所以这就是通项公式。

那么代码就很简单了


#include<bits/stdc++.h>
using namespace std;

const int si=5e5+8;
int n,m,root;
struct Tree{
	int ver,head,Next;
}e[si<<1];
int cnt=0;
void add(int u,int v){
	e[++cnt].ver=v;e[cnt].Next=e[u].head;
	e[u].head=cnt;
}
int dep[si];
int f[si][20];

void dfs(int i,int fa){
    dep[i]=dep[fa]+1;
    f[i][0]=fa;
    for(register int j=1;j<18;++j){
        f[i][j]=f[f[i][j-1]][j-1];
    }
    for(register int j=e[i].head;j;j=e[j].Next){
        int v=e[j].ver;
        if(v==fa) continue;
        dfs(v,i);
    }
}

int lca(int u,int v){
    if(dep[u]<dep[v]) swap(u,v);
    for(register int i=19;i>=0;--i){
        if(dep[f[u][i]]>=dep[v]) u=f[u][i];
    }
    if(u==v) return u;
    for(register int i=19;i>=0;--i){
        if(f[u][i]!=f[v][i]){
            u=f[u][i],v=f[v][i];
        }
    }
    return f[u][0];
}

pair<int,int> query(int x,int y,int z){
	int p,c;
	int rxy=lca(x,y),rxz=lca(x,z),ryz=lca(y,z);
	int mx=max(max(dep[rxy],dep[rxz]),dep[ryz]);
	if(rxy==rxz && rxz==ryz) p=rxy,c=(dep[x]-dep[p])*3;
	else if(mx==dep[rxy]) p=rxy;
	else if(mx==dep[rxz]) p=rxz;
	else if(mx==dep[ryz]) p=ryz;
	c=dep[x]+dep[y]+dep[z]-dep[rxy]-dep[ryz]-dep[rxz];
	return make_pair(p,c);
}

int main(){
    scanf("%d%d",&n,&m);
    for(register int i=1,u,v;i<n;++i){
        scanf("%d%d",&u,&v);
        add(u,v),add(v,u);
    }
    dfs(1,0);
    for(register int i=1;i<=m;++i){
    	int p1,p2,p3;
    	scanf("%d%d%d",&p1,&p2,&p3);
    	pair<int,int>ans=query(p1,p2,p3);
    	printf("%d %d\n",ans.first,ans.second);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/BTeqwq/p/bzoj1787-p4281.html