[NOIP 2013提高组]货车运输 题解

发布时间 2023-10-27 17:27:35作者: Ehundategh

[NOIP 2013提高组]货车运输题解

前置知识

Kruskal 重构树(内含讲解)+任意一种LCA

题目翻译

\(n\)座城市,\(m\)条道路,\(q\)次询问,每次求两个点\(x,y\)之间所有路径的最小值的最大值。

题目分析

其实学了Kruskal重构树差不多看到这个题目就知道怎么写了。

其实就是Kruskal重构树的板子,因为Kruskal重构树满足其叶子节点之间的路径的最大值的最小值为其LCA的权值,而反向排序则可以求路径最小值的最大值。下面来详解算法。

算法简介

来浅浅说一下Kruskal重构树。

Kruskal重构树其实跟Kruskal求生成树的算法大差不差,Kruskal重构树就是将其中选出的边建成一些点,故Kruskal重构树也具有Kruskal生成树的性质。

算法流程

那么如何建立一颗Kruskal重构树呢,首先就是先对边进行排序,遍历每一条边,找到满足连接不在同一集合的两个点的一条边,新建一个节点,将该节点的权值赋为选出来的边的边权,并将该边相连的两个集合(初始为图中的点,单独用结构体保存,也可以直接建新图)连接到新建的节点的子树上,至到无边可选,这样就建好了一颗Kruskal重构树。

算法性质

新建的Kruskal重构树具有类似生成树的性质(从小到大排序则为最小生成树,从大到小排序则为最大生成树,下面都以从小到大排序举例)。

性质(A):两叶子节点之间路径的最大值的最小值,就是是两节点在最小生成树中简单路径的最大值的最小值,也是两节点在Kruskal重构树上LCA的权值。

性质(B):在一个节点的子树内,所有叶子节点之间的路径的最大值的最小值都是小于等于该节点的权值的。

性质(C):类似性质B,所有到某一点\(x\)的路径中最大值的最小值小于等于一个给定值\(Value\)的节点,全都在满足是\(x\)的祖先的,最浅的权值小于等于\(Value\)的节点的子树内。

性质(A)证明:首先我们先证明,两叶子节点之间路径的最大值的最小值,就是两节点在最小生成树中简单路径的最大值的最小值。其实这个很好证明,我们采用反证法,如果不是两节点在最小生成树中简单路径的最大值的最小值,那么我们在使用Kruskal进行最小生成树生成的时候,就会选用该最小的边作为连接这两个端点代表的集合的边,那么我们假设的最小生成树就不是真正的最小生成树。然后我们来证明两叶子节点之间路径的最大值的最小值,就是两节点在Kruskal重构树上LCA的权值。其实这个也很好证明,我们知道Kruskal重构树上两个点的父节点的权值,即是两个点所代表的集合的连边,而在我们都从小到大排序的过程中,我们连接两个连通块的边一定是所有能连接这两个集合的边中最小的边。那么我们就可以知道从\(x\)\(y\),在极限情况下一定要经过值为LCA(x,y)的权值的边,而且由于我们取的是最小值,所以命题得证。

性质(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;
}