小明送宝藏(最大生成树+倍增算法优化)

本文探讨了一种使用最大生成树和倍增算法解决路径最小权重问题的方法,通过预处理和优化,实现了O(logV)的时间复杂度,适用于解决货车运输宝藏的最优化问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

S国有 N 座城市,编号依次从 1 到 N,城市之间有 M 条双向的道路。每一条道路对每辆车都有一个
最大载重量。小明意外发现了一批宝藏,精心策划了下,他计划用 Q 辆货车分头秘密的运送这批宝藏。
他想知道每辆车在不超过道路最大载重量的情况下,最多能运送宝藏的重量(此处忽略货车的重量,
只考虑宝藏的重量)。  输入格式
 输入第一行输入两个正整数N,M(0<N<10,000,0<M<50,000),之间用一个空格隔开,表示S国的 N 座城市和 M 条道路。  接下来输入 M 行,每行输入三个正整数 x、y、z,每两个整数之间用一个空格隔开,表示编号为 x 的城市和编号为 y 的城市之间有一条最大载重量为 z的道路。x不等于 y,两座城市之间可能有多条道
路。  接下来输入一行,输入一个正整数Q(0<Q<30,000),表示有 Q 辆货车在运送宝藏。  接下来输入 Q 行,每行输入两个整数 a、b,之间用一个空格隔开,表示一辆货车需要从 a 城市运输
宝藏到 b 城市,注意:a 不等于 b。  输出格式:输出一共有 Q行,每行输出一个整数,表示对于每一辆货车,在不超过道路最大载重量的
情况下,最多能运送宝藏的重量。如果货车不能到达目的地,输出 −1。
样例输入
5 4
1 2 4
2 3 3
3 4 5
3 1 1
2
1 3
2 4
样例输出
3
3

首先这道题得预处理一下,因为每两条路之间有不止一条路
定理1:最大生成树中任意两点之间的权边的最小值是所有生成树中该两点的权边最小值集合中的最大值

证明:
由于从u到v点一定有一条通路相连,如果另外有一条边加入改生成树后会形成另外一条从u到v的通路(包括形成一个包含u-v边的环的情况),如果该边的权值比通路中的最小的权边要大,则可以取代最小的边形成更大的生成树,矛盾。所以不存在这种边了。

所以,这道题我们得用最大生成树优化一下。
但是形成最大生成树之后我们又该如何搜索两个点通路间的最小边的权值是多少呢,如果用遍历搜索就是O(V)的复杂度。但是如果用倍增算法的话时间复杂度就会降到O(logV)

1:这里讲一下如何用倍增算法来记录最小权边
我们用baozang[x][i]的值来记录从x点到它的2^i倍祖先的最小权值。
dfs深度搜索的时候可以用baozang[x][0]记录x点到父亲的边权值【最大生成树中每个点只有一个父亲,根节点除外】
然后再用baozang[x][i]=min(baozang[x][father[x][i-1]],baozang[father[x][i-1]][i])递推式去更新每个点的baozang数组;

2,讲一下怎么利用baozang数组
我们在lca函数里面可以利用倍增算法,寻找到任意两点x,y的最近公共祖先。所以会形成两个通路(从x,y分别到最近公共祖先的通路)在寻找的时候同时利用min_x,和min_y来记录这个通路上的最小边。注意利用倍增二分跳跃的最后x,y两点的父节点才是公共祖先。所以还需要最后更新一下min_x和min_y的值。然后返回min_x和min_y的最小值。

直接上代码

#include <iostream>
#include <string.h>
#include <algorithm>
using namespace std;
const int MAX_N=10000;
const int MAX_M=50000;
const int inf=0x3f3f3f3f;
int head[MAX_N];
int depth[MAX_N];
int fa[MAX_N][21];
int father[MAX_N];
int baozang[MAX_N][21];
bool visit[MAX_N];
int n,m,q;
int ans=0;
struct edge{
    int w;
    int to;
    int next;
}eid[MAX_N];

struct EDGE{
    int u;int v;int w;
}e[MAX_M];

bool cmp(EDGE& a,EDGE& b){
    return a.w>b.w;
}

int ancestor(int a){
    if(a==father[a])
        return a;
    else return father[a]=ancestor(father[a]);
}

int same(int a,int b){
    return ancestor(a)==ancestor(b);
}

void merge(int x,int y){
    int fa_x=ancestor(x);
    int fa_y=ancestor(y);
    father[fa_x]=fa_y;
}


void insert(int u,int v,int w){
    eid[ans].w=w;
    eid[ans].to=v;
    eid[ans].next=head[u];
    head[u]=ans++;
}

void swap(int &a,int &b){
    int temp=a;
    a=b;
    b=temp;
}

void dfs(int now,int dep,int fat,int bz){
     if(visit[now])
         return;
     visit[now]=true;
     depth[now]=dep;
     fa[now][0]=fat;
     baozang[now][0]=bz;
     for(int i=head[now];i!=-1;i=eid[i].next){
         dfs(eid[i].to,dep+1,now,eid[i].w);
     }
}

int read(){
    int w=1;
    int s=0;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')
            w*=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        s=(s<<1)+(s<<3)+ch-48;
        ch=getchar();
    }
    return w*s;
}

int lca(int x,int y){
    int min_x=inf;
    int min_y=inf;

    if(depth[x]<depth[y]){
        swap(x,y);
    }

    for(int i=20;i>=0;--i){
        if(fa[x][i]!=0 && depth[fa[x][i]]>=depth[y]) {
            min_x=min(min_x,baozang[x][i]);
            x = fa[x][i];
        }
    }

    if(x==y)
        return min_x;
    else{
        for(int i=20;i>=0;--i){
            if(fa[x][i]!=0 && fa[y][i]!=0 && fa[x][i]!=fa[y][i])
            {
                min_x=min(min_x,baozang[x][i]);
                min_y=min(min_y,baozang[y][i]);
                x=fa[x][i];
                y=fa[y][i];
            }
        }
    }

    min_x=min(min_x,baozang[x][0]);
    min_y=min(min_y,baozang[y][0]);
    return min(min_x,min_y);
}

void kruskal(){
    for(int i=1;i<=n;i++)
        father[i]=i;

    sort(e+1,e+m+1,cmp);
    int left=n;
    for(int i=1;i<=m && left>1;++i){
        int u=e[i].u;int v=e[i].v;
        if(same(u,v)==1)
            continue;
        else{
             insert(u,v,e[i].w);
             insert(v,u,e[i].w);
             left--;
             merge(u,v);
        }
    }
}

int main() {
    cin>>n>>m;
    memset(head,-1, sizeof(head));
    memset(visit, false, sizeof(visit));

    for(int i=1;i<=m;i++){
       e[i].u=read();
       e[i].v=read();
       e[i].w=read();
    }

    kruskal();

    dfs(1,1,0,0);

    for(int i=1;i<=20;++i){
        for(int j=1;i<=n;++j){
            fa[j][i]=fa[fa[j][i-1]][i-1];
            baozang[j][i]=min(baozang[j][i-1],baozang[fa[j][i-1]][i-1]);
        }
    }

    cin>>q;
    for(int i=0;i<q;++i){
        int a,b;
        a=read();
        b=read();
        cout<<lca(a,b)<<endl;
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值