P1967,ssl2267-货车运输【树上倍增LCA,最小生成树变形kruskal】

正题

题目链接:
https://www.luogu.org/problemnew/show/P1967


大意

一个无向图,每个边有个权值,若干个询问,求两个点之间的一条最短路是这条最短路上的最小权值最大。


解题思路

首先我们发现其实每两个点之间留一条路径就好了。
然后我们会发现如果x到y的路上最小权值最大是w,那么如果z有一条边连向x,那么z到y的路上最小权值就有可能是w。
其实我们可以去掉一些边只留需要的最大的边就好了,这么一看其实就是留下最大生成树。那么在树上进行求LCA,然后在之前类似RMQ一样预处理一下就可以求两个点之间的最小权值了。


代码

#include<cstdio>
#include<queue>
#include<algorithm>
#include<cmath>
using namespace std;
queue<int> dl;
struct line{
    int to,next,w,from;
};//边
struct pic{
    line a[100001];
    int ls[10001],tot;
    void addl(int x,int y,int w)
    {
        a[++tot].to=y;
        a[tot].from=x;
        a[tot].next=ls[x];
        a[tot].w=w;
        ls[x]=tot;
    }
}p,tree;//图
int t,n,m,q,x,y,z,s;
int father[10001],f[10001][31],dis[10001][31],dep[10001];
int find(int x)//并查集-kruskal
{return father[x]==x?x:father[x]=find(father[x]);}
bool cmp(line x,line y)//排序-kruskal
{return x.w>y.w;}
void bfs(int open)//广搜预处理-树上倍增
{
    dl.push(open);
    dep[open]=1;
    while (dl.size())
    {
        int x=dl.front();dl.pop();
        for (int i=tree.ls[x];i;i=tree.a[i].next)
        {
            int y=tree.a[i].to;
            if (dep[y]) continue;
            dl.push(y);
            f[y][0]=x;
            dep[y]=dep[x]+1;
            dis[y][0]=tree.a[i].w;
        }
    }
}
int LCA(int x,int y)//树上倍增
{
    int ans=2147483647;
    if (dep[x]>dep[y]) swap(x,y);
    for (int i=t;i>=0;i--)
      if (dep[f[y][i]]>=dep[x])
        ans=min(ans,dis[y][i]),y=f[y][i];
    if (x==y) return ans;
    for (int i=t;i>=0;i--)
      if (f[y][i]!=f[x][i]) 
      {
        ans=min(ans,min(dis[x][i],dis[y][i]));//统计
        x=f[x][i];
        y=f[y][i];
      }
    ans=min(ans,min(dis[x][0],dis[y][0]));
    return ans;
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        p.addl(x,y,z);
        p.addl(y,x,z);
    }
    sort(p.a+1,p.a+1+p.tot,cmp);
    for (int i=1;i<=n;i++)
      father[i]=i;
    for (int i=1;i<=p.tot;i++)
    {
        if (find(p.a[i].to)!=find(p.a[i].from))
        {
            s++;
            tree.addl(p.a[i].to,p.a[i].from,p.a[i].w);
            tree.addl(p.a[i].from,p.a[i].to,p.a[i].w);
            father[find(p.a[i].to)]=find(p.a[i].from);
            if (s==n) break;
        }
    }//kruskal
    t=(int)(log(n)/log(2))+1;
    for (int i=1;i<=n;i++)
      if (!dep[i]) 
      {
        dis[i][0]=2147483647;
        bfs(i);//广搜预处理
      }
    for (int j=1;j<=t;j++)
    {
        for (int i=1;i<=n;i++)
        {
          f[i][j]=f[f[i][j-1]][j-1];
          dis[i][j]=min(dis[i][j-1],dis[f[i][j-1]][j-1]);
        }
    }//计算树上倍增与统计路径最小值的预处理
    scanf("%d",&q);
    for (int i=1;i<=q;i++)
    {
        scanf("%d%d",&x,&y);
        if (find(x)!=find(y))
        printf("-1\n");//不连通
        else
        printf("%d\n",LCA(x,y));//输出
    }
}

转载于:https://www.cnblogs.com/sslwyc/p/9218515.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值