[NOIP 2013] 货车运输 题解

题解:

首先,题目要求我们求两点间所有路径的最小边权,我们要做的是最大化这个最小边权,我们可以搞一颗图的最大生成树,这样这个最小边权一定是最大的。我们可以考虑反证法,若存在另一条路径上的最小边权大于最大生成树上的路径的最小边权,那么显然我们可以通过断掉最大生成树上的边连另外一条边更大化这个最大生成树(建议画图理解),与最大生成树的定义矛盾。

我们可以用$Kruskal$算法求出这个最大生成树,考虑如何快速求出两个点的路径,因为两点之间的路径可以分解成为它们的$lca$到这两个点的路径,我们可以选择在求询问的两点的$lca$时结合倍增顺便将最短路径求出,具体来说就是用两个数组$f[i,j],dp[i,j]$分别表示节点$i$到它的$2^j$次方祖先节点的节点编号与路径上的最小权值。
那么有:

$dp[i,j]=min(dp[i,j-1],dp[f[i,j-1],j-1])$

这两个皆可以一遍$dfs$求出。

需要注意的细节:这个图可能是不连通的,求$dfs$时注意将所有的最大生成树都dfs一遍。

最后附上代码:

#include<bits/stdc++.h>
using namespace std;
const int N=10005,M=50005,inf=(1<<30);

int c[N],ver[M],nxt[M],edge[M],head[N],d[N],f[N][20],dp[N][20];
int n,m,cnt,tot,t,q;
bool vis[N];

struct point{
    int u,v,len;
    bool operator < (const point &a)const{return a.len<len;}
}edge1[M];

int read(){
    int x=0;
    char ch=getchar();
    while(ch<'0' || ch>'9') ch=getchar();
    while(ch>='0' && ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return x;
}

int getfather(int x){return x==c[x] ? x : c[x]=getfather(c[x]);}
void add(int x,int y,int z){ver[++tot]=y,edge[tot]=z,nxt[tot]=head[x],head[x]=tot;}

void dfs(int x){
    vis[x]=1;
    for(int i=head[x],y;i;i=nxt[i]){
        if(vis[y=ver[i]]) continue;
        d[y]=d[x]+1,f[y][0]=x,dp[y][0]=edge[i];
        for(int j=1;j<=t;++j){
            f[y][j]=f[f[y][j-1]][j-1];
            dp[y][j]=min(dp[y][j-1],dp[f[y][j-1]][j-1]);
        }
        dfs(y);
    }
}

int LCA(int x,int y){
    int ans=inf;
    if(d[x]>d[y]) swap(x,y);
    for(int i=t;i>=0;--i){
        if(d[f[y][i]]>=d[x]){
            ans=min(ans,dp[y][i]);
            y=f[y][i];
        }
    }
    if(x==y) return ans;
    for(int i=t;i>=0;i--){
        if(f[x][i]!=f[y][i]){
            ans=min(ans,min(dp[x][i],dp[y][i]));
            x=f[x][i],y=f[y][i];
        }
    }
    return ans=min(ans,min(dp[x][0],dp[y][0]));
}

int main(){
    n=read(),m=read();
    t=(int)(log(n)/log(2))+2;
    for(int i=1;i<=m;++i) edge1[++cnt].u=read(),edge1[cnt].v=read(),edge1[cnt].len=read();
    for(int i=1;i<=n;++i) c[i]=i;
    sort(edge1+1,edge1+1+cnt);
    for(int i=1;i<=cnt;++i){//Kruskal求最大生成树
        int x=edge1[i].u,y=edge1[i].v,z=edge1[i].len;
        int faa=getfather(x),fab=getfather(y);
        if(faa==fab) continue;
        c[fab]=faa;
        add(x,y,z),add(y,x,z);
    }
    for(int i=1;i<=n;++i) if(!vis[i]){d[i]=1,dfs(i);}
    q=read();
    for(int i=1,x,y;i<=q;++i){
        x=read(),y=read();
        int faa=getfather(x),fab=getfather(y);
        if(faa!=fab){printf("-1\n");continue;}//不在一个连通块内
        printf("%d\n",LCA(x,y));
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/Asika3912333/p/11823473.html