[NOIP 2013提高组]货车运输 题解
[NOIP 2013提高组]货车运输题解
前置知识
Kruskal 重构树(内含讲解)+任意一种LCA
题目翻译
题目分析
其实学了Kruskal重构树差不多看到这个题目就知道怎么写了。
其实就是Kruskal重构树的板子,因为Kruskal重构树满足其叶子节点之间的路径的最大值的最小值为其LCA的权值,而反向排序则可以求路径最小值的最大值。下面来详解算法。
算法简介
来浅浅说一下Kruskal重构树。
Kruskal重构树其实跟Kruskal求生成树的算法大差不差,Kruskal重构树就是将其中选出的边建成一些点,故Kruskal重构树也具有Kruskal生成树的性质。
算法流程
那么如何建立一颗Kruskal重构树呢,首先就是先对边进行排序,遍历每一条边,找到满足连接不在同一集合的两个点的一条边,新建一个节点,将该节点的权值赋为选出来的边的边权,并将该边相连的两个集合(初始为图中的点,单独用结构体保存,也可以直接建新图)连接到新建的节点的子树上,至到无边可选,这样就建好了一颗Kruskal重构树。
算法性质
新建的Kruskal重构树具有类似生成树的性质(从小到大排序则为最小生成树,从大到小排序则为最大生成树,下面都以从小到大排序举例)。
性质(A):两叶子节点之间路径的最大值的最小值,就是是两节点在最小生成树中简单路径的最大值的最小值,也是两节点在Kruskal重构树上LCA的权值。
性质(B):在一个节点的子树内,所有叶子节点之间的路径的最大值的最小值都是小于等于该节点的权值的。
性质(C):类似性质B,所有到某一点
性质(A)证明:首先我们先证明,两叶子节点之间路径的最大值的最小值,就是两节点在最小生成树中简单路径的最大值的最小值。其实这个很好证明,我们采用反证法,如果不是两节点在最小生成树中简单路径的最大值的最小值,那么我们在使用Kruskal进行最小生成树生成的时候,就会选用该最小的边作为连接这两个端点代表的集合的边,那么我们假设的最小生成树就不是真正的最小生成树。然后我们来证明两叶子节点之间路径的最大值的最小值,就是两节点在Kruskal重构树上LCA的权值。其实这个也很好证明,我们知道Kruskal重构树上两个点的父节点的权值,即是两个点所代表的集合的连边,而在我们都从小到大排序的过程中,我们连接两个连通块的边一定是所有能连接这两个集合的边中最小的边。那么我们就可以知道从
性质(B)和性质(C)可以通过性质(A)简单证明得知,就不过多赘述。
题目解法
其实这个时候已经很明晰了,这个题就是运用了Kruskal重构树的性质(A),只需要建立一颗Kruskal重构树,只不过要反向排序,然后在树上找两节点的LCA即可。
注意:每次求解要判断两节点是否连通,也就是判断两者是否在一个并查集。
下面附上代码(代码很短,100多行,比树剖短多了(代码太丑了呜呜呜))
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAXN 10010
#define MAXM 50010
using namespace std;
int cnt=0,Father[MAXN<<1][21],Fa[MAXN<<1];
int n,m;
struct E{
int St,Ed;
int Value;
}Edge[MAXM];
bool cmp(E a,E b){return a.Value>b.Value;}
struct N{
int Value,Depth;
int Father,LeftSon,RightSon;
}Node[MAXN<<1];
int Find(int x){return Fa[x]==x?x:Fa[x]=Find(Fa[x]);}
void Merge(int x,int y,int z){
Node[z].LeftSon=Find(x);
Node[Find(x)].Father=z;
Father[Find(x)][0]=z;
Fa[Find(x)]=z;
Node[Find(y)].Father=z;
Father[Find(y)][0]=z;
Node[z].RightSon=Find(y);
Fa[Find(y)]=z;
}
void Get_Depth(){
queue <int> Line;
for(int i=1;i<=cnt;i++){
if(!Node[i].Father){Line.push(i); Node[i].Depth=1;}
}
while(!Line.empty()){
int Out=Line.front();
Line.pop();
if(!(Node[Out].LeftSon&&Node[Out].RightSon)) continue;
Node[Node[Out].LeftSon].Depth=Node[Node[Out].RightSon].Depth=Node[Out].Depth+1;
Line.push(Node[Out].LeftSon);
Line.push(Node[Out].RightSon);
}
}
void Kruskal_Rebuilding(){
sort(Edge+1,Edge+m+1,cmp);
for(int i=1;i<=m;i++){
if(Find(Edge[i].St)==Find(Edge[i].Ed)) continue;
Merge(Edge[i].St,Edge[i].Ed,++cnt);
Node[cnt].Value=Edge[i].Value;
}
}
void Init(){
for(int i=1;i<=19;i++){
for(int j=1;j<=cnt;j++){
Father[j][i]=Father[Father[j][i-1]][i-1];
}
}
}
int LCA(int x,int y){
if(Node[x].Depth<Node[y].Depth)swap(x,y);
for(int i=19;i>=0;i--){
if(Node[Father[x][i]].Depth>Node[y].Depth){
x=Father[x][i];
}
}
if(Node[x].Depth>Node[y].Depth)x=Father[x][0];
if(x==y) return x;
for(int i=19;i>=0;i--){
if(Father[x][i]!=Father[y][i]){
x=Father[x][i];
y=Father[y][i];
}
}
return Father[x][0];
}
void DSInit(){for(int i=1;i<=2*n;i++){Father[i][0]=i; Fa[i]=i;}}
int main(){
scanf("%d%d",&n,&m);
int In1,In2,In3;
for(int i=1;i<=m;i++){
scanf("%d%d%d",&In1,&In2,&In3);
Edge[i]={In1,In2,In3};
}
DSInit();
cnt=n;
Kruskal_Rebuilding();
Get_Depth();
Init();
int Q;scanf("%d",&Q);
for(int i=1;i<=Q;i++){
scanf("%d%d",&In1,&In2);
int L;
if(Find(In1)==Find(In2)) L=LCA(In1,In2);
else{printf("-1\n");continue;}
printf("%d\n",Node[L].Value);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】