单源最短路练习(一)

单源最短路的建图方式

热浪

题目链接:热浪
分析:比较裸的一道题,直接建图再套模板,我就直接用spfa水过了。
代码实现:

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int N=2510,M=12410;
int n,m,str,ed;
int dist[N];
int h[N],e[M],ne[M],idx=1,w[M];//链式前向星存储图
bool st[N];
queue<int> q;
void add(int a,int b,int c){
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void spfa(){
    memset(dist,0x3f,sizeof dist);
    dist[str]=0;
    q.push(str);
    st[str]=1;
    while(q.size()){
        auto t=q.front();
        q.pop();
        st[t]=0;
        for(int i=h[t];i;i=ne[i]){
            int j=e[i];
            if(dist[j]>dist[t]+w[i]){
                dist[j]=dist[t]+w[i];
                if(!st[j]) q.push(j),st[j]=1;//这里st[j]去掉也不错,但是效率会低一些
            }
        }
    }
}
int main(){
    cin>>n>>m>>str>>ed;
    for(int i=1;i<=m;i++){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    spfa();
    cout<<dist[ed]<<endl;
    return 0;
}

信使

题目链接:信使
分析:对于此题,我们发现,对于除1外的每个点,通信所需的最短时间就是1到此点的最短路距离,而所有点全部完成通信的时间就是所有点到1(除1外)的最短路中的最大值,因此我们只需求一下1到所有点(除1外)的最短路即可。
代码实现:

#include<iostream>
#include<cstring>
using namespace std;
const int INF=0x3f3f3f3f;
int n,m;
const int N=110;
int g[N][N];
int main(){
    cin>>n>>m;
    memset(g,0x3f,sizeof g);
    //这里我们不用初始化g[i][i]为0,因此更新的时候不会用到它,而更新的时候不用这个状态也对
    while(m--){
        int a,b,c;
        cin>>a>>b>>c;
        g[a][b]=g[b][a]=min(g[a][b],c);//防止有多条边
    }
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                g[i][j]=min(g[i][j],g[i][k]+g[k][j]);
    int res=0;
    for(int i=2;i<=n;i++)//注意从2开始,如果从1开始,因为我们未把g[1][1]初始化为0,会导致错误
        if(g[1][i]==INF) {
            res=-1;
            break;
        }
        else res=max(res,g[1][i]);
    cout<<res;
    return 0;
}

香甜的黄油

题目链接:香甜的黄油
分析:对于这道题,我们可以枚举每个牧场放糖,然后求出这个牧场到其它所有牧场的最短路,然后枚举每头牛所在牧场,把这些距离都求出来并取最小的一个。如果直接上dijkstra的话可能会超时吧,这里我们用spfa,这题出题人并没有卡spfa。
代码实现:

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int INF=0x3f3f3f3f;
int s,n,m,tmp;
const int N=810,M=2910;
int res=INF;
int id[N],e[M],ne[M],idx=1,h[N],w[M];
bool st[N];
int dist[N];
queue<int> q;
void add(int a,int b,int c){
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
int spfa(int str){
    memset(dist,0x3f,sizeof dist);
    q.push(str);
    st[str]=1;
    dist[str]=0;
    while(q.size()){
        auto t=q.front();
        q.pop();
        st[t]=0;
        for(int i=h[t];i;i=ne[i]){
            int j=e[i];
            if(dist[j]>dist[t]+w[i]){
                dist[j]=dist[t]+w[i];
                if(!st[j]) q.push(j),st[j]=1;
            }
        }
    }
    int ans=0;
    for(int i=1;i<=s;i++)
        if(dist[id[i]]==INF) return INF;//一旦有两个牧场不连通,直接返回INF
        else ans+=dist[id[i]];
    return ans;
}
int main(){
    cin>>s>>n>>m;
    for(int i=1;i<=s;i++) cin>>id[i];//记录每头牛所在的牧场
    while(m--){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c),add(b,a,c);
    }
    for(int i=1;i<=n;i++) res=min(res,spfa(i));
    cout<<res<<endl;
    return 0;
}

最小花费

题目链接:最小花费
分析:比较有意思的一道题,我们分别用spfa和dijkstra讲解一遍。
我们先考虑,假设A点的钱数为SA,经过每个点后剩余的钱为原来的Ci倍,它经过所有点到B后有一个等式SA*C1*C2*C3......=100,我们要求SA的最小值,就要求那一堆C的乘积的最大值,又因为Ci大于0小于等于1,所以越乘越小,接下来我们讨论两种做法。
spfa:spfa是基于它的松弛操作,对于一个起点,如果我们能通过它的出边更新邻点(所有点初始被初始化为0,起点初始化为1),也就是把C的乘积变得更小,就操作并把这个点加入队列中,因此spfa求最短路、最长路都是可以的。
代码实现:

#include<iostream>
#include<queue>
using namespace std;
int n,m,A,B;
const int N=2010,M=2e5+10;
int e[M],ne[M],idx=1,h[N];
double w[M];
double dist[N];
bool st[N];
queue<int> q;
void add(int a,int b,double c){
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void spfa(){
    dist[B]=1;
    st[B]=1;
    q.push(B);
    while(q.size()){
        auto t=q.front();
        q.pop();
        st[t]=0;
        for(int i=h[t];i;i=ne[i]){
            int j=e[i];
            if(dist[j]<dist[t]*w[i]){
                dist[j]=dist[t]*w[i];
                if(!st[j]) q.push(j),st[j]=1;
            }
        }
    }
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        int x,y,z;
        cin>>x>>y>>z;
        double c=(100.0-z)/100;//转化为我们分析中的c
        add(x,y,c);
        add(y,x,c);
    }
    cin>>A>>B;
    spfa();//把B当作起点
    printf("%.8lf",100/dist[A]);
    return 0;
}

dijkstra:我们知道,dijkstra是基于贪心思想的,对于求最短路问题,我们先找出距离终点最短的一个,初始时就是它自己,然后用它更新别的点,但是它不能求最长路问题(这里的证明我也不会,《算法导论》上应该有),而对于我们要求最大的C,我们每次找出最大的C来更新,初始时也是它自己,然后用相似的方法来更新,我们发现也能AC。(在这里我有个猜测,dijkstra不能求最长路是因为初始时到终点最近的不是终点自己。)具体证明我是真的不会,我太菜了,留下了不争气的泪水
代码实现:(我不想再写了,就直接贴y总的了)

#include <iostream>
using namespace std;
const int N = 2010;
int n, m, S, T;
double g[N][N];
double dist[N];
bool st[N];
void dijkstra(){
    dist[S] = 1;//起点初始化为1,其它点都是0
    for (int i = 1; i <= n; i ++ ){
        int t = -1;
        //找出最大的C
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] < dist[j]))
                t = j;
        st[t] = true;
        for (int j = 1; j <= n; j ++ )
            dist[j] = max(dist[j], dist[t] * g[t][j]);//更新
    }
}
int main(){
    scanf("%d%d", &n, &m);
    while (m -- ){
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        double z = (100.0 - c) / 100;//转化为我们分析中的C
        g[a][b] = g[b][a] = max(g[a][b], z);
    }
    cin >> S >> T;
    dijkstra();
    printf("%.8lf\n", 100 / dist[T]);
    return 0;
}

顺带一提,对于本题,dijkstra:570ms左右,spfa:242ms左右。

最优乘车

题目链接:最优乘车
分析:这题的建图就有些技巧了,首先,这道题肯定有很多解法(我猜的),而我们既然是单源最短路练习,就用单源最短路求法来求吧。我们对于每一条路线,前面的一个点到后面的任意一个点的距离都设为1,然后我们求出起点到终点的最短路,再减去1就是答案(因为有一次是再同一条路线上转移的),不过还有一种特殊情况,起点和终点重合时,最短路是0,减去1就是-1了,因此我们要特殊处理一下。
代码实现:

//艹,怎么感觉我写的bfs一股spfa味
#include<iostream>
#include<sstream>
#include<queue>
#include<cstring>
using namespace std;
int n,m,tmp;
const int N=510,M=25010,INF=0x3f3f3f3f;
int e[M],ne[M],idx=1,h[N];
int stop[N];
int dist[N];
queue<int> q;
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void bfs(){
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    q.push(1);
    while(q.size()){
        auto t=q.front();
        q.pop();
        for(int i=h[t];i;i=ne[i]){
            int j=e[i];
            if(dist[j]==INF){//因为是bfs,第一次更新时一定是最小值
                dist[j]=dist[t]+1;
                q.push(j);
            }
        }
    }
}
int main(){
    string s;
    cin>>m>>n;
    getline(cin,s);//吐掉\n,因为cin不会读取末尾的\0
    for(int i=1;i<=m;i++){
        getline(cin,s);
        stringstream ss(s);//se存进ss中
        int cnt=0;
        while(ss>>tmp) stop[cnt++]=tmp;//把数字读入tmp中
        for(int j=0;j<cnt;j++)
            for(int k=j+1;k<cnt;k++)
                add(stop[j],stop[k]);//加边
    }
    bfs();//因为边权为1,直接bfs
    if(dist[n]==INF) puts("NO");//无解
    else cout<<max(dist[n]-1,0)<<endl;
    return 0;
}

昂贵的聘礼

题目链接:昂贵的聘礼
分析:最有意思的一题!读题都把我读晕了快。最难的还是考虑如何建图比较easy。不得不说这题的建图方法真是太牛逼了。。。我们人为设计一个虚拟起点,它到所有物品的距离都是这些物品的价格,然后再把能等效替代的物品相连,代价就是替代后所需的金币,然后这题就变成了一个最短路问题,起点是虚拟起点,终点是1,但是这题还有一个约束,就是等级差,我们枚举所有可能的等级范围,然后求最短路的同时只更新符合等级要求的点即可,至此,此题完美解决!
代码实现:
我们发现,只要我们会建图了,后面的随便写!
这是我写的堆优化dijkstra:117ms,可能因为边数比较多

#include<iostream>
#include<queue>
#include<cstring>
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;
const int N=110,M=1e4+10,INF=0x3f3f3f3f;
int e[M],ne[M],idx,h[N],w[M],level[N];
int m,n,res=INF;
int dist[N];
bool st[N];
void add(int a,int b,int c){
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
int dijkstra(int left){
    priority_queue<PII,vector<PII>,greater<PII>> heap;
    memset(st,0,sizeof st);
    memset(dist,0x3f,sizeof dist);
    dist[0]=0;
    heap.push({0,0});
    //刚开始多加了这一句st[0]=1,结果调了半天,气死了
    while(heap.size()){
        auto t=heap.top();
        heap.pop();
        int tmp=t.second;
        if(st[tmp])continue;
        st[tmp]=1;
        for(int i=h[tmp];~i;i=ne[i]){
            int j=e[i];
            if(dist[j]>dist[tmp]+w[i]&&level[j]>=left&&level[j]<=left+m){//等级也符合要求才能更新
                dist[j]=dist[tmp]+w[i];
                heap.push({dist[j],j});
            }
        }
    }
    return dist[1];
}
int main(){
    memset(h,-1,sizeof h);
    cin>>m>>n;
    for(int i=1;i<=n;i++){
        int p,x;
        cin>>p>>level[i]>>x;
        add(0,i,p);
        while(x--){
            int t,v;
            cin>>t>>v;
            add(t,i,v);
        }
    }
    for(int i=level[1]-m;i<=level[1];i++)//枚举等级区间的左边界,右边界是左边界+m,这里因为间接交易的双方等级差也不能超过m,因此等级区间的长度为m
        res=min(res,dijkstra(i));
    cout<<res<<endl;
    return 0;
}

这是y总的朴素dijkstra:72ms

#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110, INF = 0x3f3f3f3f;
int n, m;
int w[N][N], level[N];
int dist[N];
bool st[N];
int dijkstra(int down, int up){
    memset(dist, 0x3f, sizeof dist);
    memset(st, 0, sizeof st);
    dist[0] = 0;
    for (int i = 1; i <= n + 1; i ++ ){
        int t = -1;
        for (int j = 0; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                 t = j;
        st[t] = true;
        for (int j = 1; j <= n; j ++ )
            if (level[j] >= down && level[j] <= up)
                dist[j] = min(dist[j], dist[t] + w[t][j]);
    }
    return dist[1];
}
int main(){
    cin >> m >> n;
    memset(w, 0x3f, sizeof w);
    for (int i = 1; i <= n; i ++ ) w[i][i] = 0;
    for (int i = 1; i <= n; i ++ ){
        int price, cnt;
        cin >> price >> level[i] >> cnt;
        w[0][i] = min(price, w[0][i]);
        while (cnt -- ){
            int id, cost;
            cin >> id >> cost;
            w[id][i] = min(w[id][i], cost);
        }
    }
    int res = INF;
    for (int i = level[1] - m; i <= level[1]; i ++ ) res = min(res, dijkstra(i, i + m));
    cout << res << endl;
    return 0;
}
  • 1
    点赞
  • 1
    收藏
  • 打赏
    打赏
  • 1
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:数字20 设计师:CSDN官方博客 返回首页
评论 1

打赏作者

algorithmwqy

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

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值