05 算法竞赛进阶指南最短路习题+例题 (附代码+注释+思路)-01

acwing 340 通信线路

在这里插入图片描述

题目思路

  1. 二分做法思路
    定义在[0,1000001] 这个区间中的性质如下:
    对于区间中的某一个点x。
    求出1走到N,最少经过的长度大于x的边的是否小于等于k。
    求出从1到N最少经过几条长度大于x的边。
    可以将所有的边权分类:大于等于x的为1,小于的为0。
    采用双端队列的BFS来求从1到N的最短路。

解题代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;

const int N = 1e3+10,M = 2e5+10;
int h[N],e[M],w[M],ne[M],idx;
int dist[N];
deque<int> q; //双端队列
bool st[N];
int n,m,k; 


bool check(int mid)  // 二分判断条件
{
    memset(dist,0x3f,sizeof dist);
    memset(st, 0, sizeof st);
    dist[1] = 0;
    q.push_back(1);
    while(!q.empty())
    {
        int t = q.front(); q.pop_front();
        if(st[t]) continue;             // 如果扩展过了 跳过
        st[t] = true;
        
        for(int i=h[t]; ~i; i = ne[i])
        {
            int j = e[i],v = w[i]>mid;    /// v是判断该边是0 还是1
            if(dist[j]>dist[t]+v)
            {
                dist[j] = dist[t]+v;
                if(!v) q.push_front(j);  // 如果这个边为0,我们先让这个点加进去,找他后面的为1的边
                else q.push_back(j);     // 如果这个边为1,我们先把这个点后放进去,看看有没有其他的边到此累加
            }
        }
        
    }
    if(dist[n]<=k) return true;
    return false;
    
    
}
void add(int a,int b,int c)
{
    e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx++;
}


int main()
{
    ios::sync_with_stdio(false); cin.tie(0);
    memset(h, -1, sizeof h);  /// 邻接表 要初始化头结点
    cin>>n>>m>>k;
   
    for(int i=1; i<=m; i++)
    {
        int a,b,c; cin>>a>>b>>c;
        add(a,b,c); add(b,a,c); // 双向边
    }
    
    // 这里l和r比区间小一和大一,相当于哨兵判断不符合条件的状态,也就是走不到N的点
    int l = 0,r = 1000001;  
    while(l<r)  // 二分函数
    {
        int mid = l+r >>1;
        if(check(mid)) r = mid;
        else l = mid+1;
    }
    if(r==1000001) r = -1;
    cout<<r<<"\n";
    
}

acwing 341 最优贸易

在这里插入图片描述
在这里插入图片描述

题目思路

  1. 题意思路就是让我们在经过从1到n的道路上,找两个城市间的水晶球差值最大,水晶球价值低的先经过,水晶球价值高的后经过。
  2. 图中双向通行的道路可以看作两条方向相反的单向通性道路。我们把这张图视为有向图。除此之外,我们还需要建立一张反图(在原图基础上把所有的边的方向取反后的图)保存在另一个邻接表中。
  3. 先以1为起点,在原图上使用 SPFA 算法,求出一个数组D,其中 D[x] 表示 从节点 1 到节点x的所有路径当中的能够经过的最小的节点权值。D 数组的计算过程和单源最短路径的计算过程类似,只需要把最短路中的用“D[x] + w(x,y) 更新 D[y] ” 改为 “用 min(D[x],price[y]) 更新D[y]”即可,其中 price[y] 表示点y的 商品价格。上面的更新不满足Dijkstra算法的贪心性质,所以要用SPFA算法。
  4. 再以 n 为起点,在反图上用SPFA算法,求出数组 F,其中F[x] 表示在原图上从x到n的水晶球的最大值。F数组的运算与D数组类似。
  5. 最后枚举每个节点的x,求出 F[x] - D[x] 的最大值就是我们的答案。
  6. 因为这道题是随机数据,没有特殊构造,所以可以使用SPFA算法。

解题代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>


using namespace std;
const int N = 1e6+10;
int h[N],e[N],ne[N],idx;        /// 邻接表存原图
int h_fan[N],e_fan[N],ne_fan[N],idx_fan;    /// 邻接表存反图
int w[N];
int d[N],f[N];
bool st[N];
int n,m;

void add(int a,int b)
{
    e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
void add1(int a,int b)
{ 
    e_fan[idx_fan] = b,ne_fan[idx_fan] = h_fan[a],h_fan[a] = idx_fan++;
}
 
void spfa(int u)     /// 从1开始预处理 d数组的最小值
{
    memset(st, 0, sizeof st);
    memset(d,0x3f,sizeof d);        /// 因为是最小值 要初始化一下全为最大值
    queue<int> q;  q.push(u); st[u] = true; d[u] = w[u];
    while(!q.empty())
    {
        int t = q.front(); q.pop();
        st[t] = false;          /// 出队了就解除标记
        for(int i = h[t]; ~i; i=ne[i])
        {
            int j = e[i];
            if(d[j]>min(d[t],w[j]))
            {
                d[j] = min(d[t],w[j]);
                if(!st[j])
                {
                    st[j] = true;
                    q.push(j);
                }
            }
        }
    }
}

void spfa1(int u)   /// 处理f的最大值
{
    memset(st, 0, sizeof st);
    queue<int> q;  q.push(u); st[u] = true; f[u] = w[u];
    while(!q.empty())
    {
        int t = q.front(); q.pop();
        st[t] = false;          /// 出队就解除标记
        for(int i = h_fan[t]; ~i; i=ne_fan[i])
        {
            int j = e_fan[i];
            if(f[j]<max(f[t],w[j]))
            {
                f[j] = max(f[t],w[j]);
                if(!st[j])
                {
                    st[j] = true;
                    q.push(j);
                }
            }
        }
    }
}

int main()
{
    ios::sync_with_stdio(false); cin.tie(0);
    memset(h, -1, sizeof h);
    memset(h_fan, -1, sizeof h_fan);  // 初始化头节点
    cin>>n>>m;
    for(int i=1; i<=n; i++) cin>>w[i];
    for(int i=1; i<=m; i++)
    {
        int a,b,c; cin>>a>>b>>c;
        if(c==1) add(a,b),add1(b,a);
        else add(a,b),add(b,a),add1(a,b),add1(b,a);
    }
    spfa(1);
    spfa1(n);
    int ans =0;
    for(int i=1; i<=n; i++)   /// 遍历求出能够交易获得的最大值
    {
        if(f[i]>d[i]) ans = max(ans,f[i]-d[i]);
    }
    cout<<ans<<"\n";
    return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

q2090988808

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值