【题解】紧急集合 / 聚会(LCA)
题目描述
欢乐岛上有个非常好玩的游戏,叫做“紧急集合”。在岛上分散有 n 个等待点,有 n−1 条道路连接着它们,每一条道路都连接某两个等待点,且通过这些道路可以走遍所有的等待点,通过道路从一个点到另一个点要花费一个游戏币。
参加游戏的人三人一组,开始的时候,所有人员均任意分散在各个等待点上(每个点同时允许多个人等待),每个人均带有足够多的游戏币(用于支付使用道路的花费)、地图(标明等待点之间道路连接的情况)以及对话机(用于和同组的成员联系)。当集合号吹响后,每组成员之间迅速联系,了解到自己组所有成员所在的等待点后,迅速在 n 个等待点中确定一个集结点,组内所有成员将在该集合点集合,集合所用花费最少的组将是游戏的赢家。
小可可和他的朋友邀请你一起参加这个游戏,由你来选择集合点,聪明的你能够完成这个任务,帮助小可可赢得游戏吗?
输入格式
第一行两个正整数 n 和 m,分别表示等待点的个数(等待点也从 1 到 n 进行编号)和获奖所需要完成集合的次数。
随后 n−1 行,每行两个正整数 a,b,表示编号为 a 和编号为 b 的等待点之间有一条路。
随后 m 行,每行用三个正整数 x,y,z,表示某次集合前小可可、小可可的朋友以及你所在等待点的编号。
输出格式
输出共 m 行,每行两个用空格隔开的整数 p,c。其中第 i 行表示第 i 次集合点选择在编号为 p 的等待点,集合总共的花费是 c 个游戏币。
输入输出样例
6 4 1 2 2 3 2 4 4 5 5 6 4 5 6 6 3 1 2 4 4 6 6 6
5 2 2 5 4 1 6 0
说明/提示
对于 40% 的数据,n≤2×10^3,m≤2×10^3。
对于 100% 的数据,1≤x,y,z≤n≤5×10^5,1≤m≤5×10^5。
正文
前置芝士:LCA
看题目描述明显是用树,但是怎么实现呢?
让我们分开来看:当一组中只有两人时,花费金币最少的路线就是两人去往其最近公共祖先的路线,此问题就可以被简化成为找最近公共祖先的问题了。
接下来人数增加到三人,就是在原有的路线的基础上增加一个选择,然后判断花费最少的路线,但是如果把所有路线的花费都求出来显然会时间超限,所以这里就有一个优化的措施:
先说结论:此三人的所有最近公共祖先必定有两个重合,余下的不重合公共祖先即为最优解。
以下证明过程
首先,三人通往其最近公共祖先的路径一定是如下形状:
(注:实心点为出发点,空心点为公共祖先,中途路线省略)
如前文所说,我们可以把三个人的最优路线看作在两个人的最近路线上添加一条路线,所以形状必定如图所示。
简化为上图之后我们就可以很直接地看出来最优解的所在啦~也就是上文中的结论
因为每条路线的花费是一样的,所以花费的游戏币数量就是边的条数,我们可以使用深度算出。
明白这些之后就可以上代码辣!
code:
1 #include<bits/stdc++.h> 2 #define ll long long 3 using namespace std; 4 const int M=5*1e5+10; 5 int n,m; 6 int f[M][25],dep[M];//f数组用于计算LCA,dep数组记录深度 7 int nxt[2*M],last[2*M],to[2*M];//记录边 8 int tot=0; 9 inline int read(){//快读不解释 10 int x=0,f=1; 11 char ch=getchar(); 12 while(ch<'0'||ch>'9'){ 13 if(ch=='-') 14 f=-1; 15 ch=getchar(); 16 } 17 while(ch>='0'&&ch<='9'){ 18 x=(x<<1)+(x<<3)+(ch^48); 19 ch=getchar(); 20 } 21 return x*f; 22 } 23 int add(int a,int b)//读边不解释 24 { 25 nxt[++tot]=last[a]; 26 last[a]=tot; 27 to[tot]=b; 28 nxt[++tot]=last[b]; 29 last[b]=tot; 30 to[tot]=a; 31 } 32 inline int lca(int x,int y)//求lca不解释 33 { 34 if(dep[x]<dep[y]) swap(x,y); 35 for(int i=24;i>=0;i--){ 36 if(dep[f[x][i]]>=dep[y]) x=f[x][i]; 37 if(x==y) return x; 38 } 39 for(int i=24;i>=0;i--){ 40 if(f[x][i]!=f[y][i]){ 41 x=f[x][i]; 42 y=f[y][i]; 43 } 44 } 45 return f[x][0]; 46 } 47 void work(int x,int y)//预处理函数 48 { 49 dep[x]=dep[y]+1; 50 for(int i=0;i<=23;i++) 51 f[x][i+1]=f[f[x][i]][i]; 52 for(int i=last[x];i;i=nxt[i]){ 53 int tool=to[i]; 54 if(tool==y) continue; 55 f[tool][0]=x; 56 work(tool,x); 57 } 58 } 59 int main() 60 { 61 n=read();m=read(); 62 for(int i=1;i<n;i++) 63 add(read(),read());//读入边 64 work(1,0); 65 int x,y,z; 66 int a,b,c; 67 for(int i=1;i<=m;i++){ 68 x=read();y=read();z=read(); 69 a=lca(x,y); 70 b=lca(x,z); 71 c=lca(y,z); 72 if(a==b) printf("%d %d\n",c,dep[x]-dep[a]+dep[y]-dep[c]+dep[z]-dep[a]);//此处三个计算式都由图像推出,可以在我放在上文的图上进行标注后推导出来 73 else if(a==c) printf("%d %d\n",b,dep[y]-dep[a]+dep[z]-dep[c]+dep[x]-dep[b]); 74 else if(b==c) printf("%d %d\n",a,dep[z]-dep[b]+dep[y]-dep[b]+dep[x]-dep[a]); 75 } 76 return 0; 77 }
完结撒花~