[AcWing算法提高课]之图论 单源最短路的综合应用(C++题解)

目录

1)热浪(板子题)

(朴素dijkstra) O(n2)

(堆优化dijkstra) O((n+m)logm)

(spfa) O(m)

2)信使

3)香甜的黄油

4)最小花费

5) 最优乘车

6)昂贵的聘礼

7)新年好

8)通信线路


1)热浪(板子题)

活动 - AcWing

 

 题意概述

输入样例 :

第一行      :给定  T个点        C条边        起点Ts        终点Te

剩下的C行是对 某一条边的描述        :从a-->>b 有 一条权重为 c  的边

根据数据范围推出用哪种最短路:

 本题中 点数的最大值为 2500,边数的范围是 6200

下面是三种的最短路的算法 的实现方式

(朴素dijkstra) O(n2)

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 2510, M = 6210;

int n, m, s, t;
int g[N][N];
int dist[N];
bool st[N];

void dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    memset(st, 0, sizeof st);
    dist[s] = 0;
    for (int i = 1; i <= n; i ++ )
    {
        int t = -1;
        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] = min(dist[j], dist[t] + g[t][j]);
    }
}
int main()
{
    memset(g, 0x3f, sizeof g);
    cin >> n >> m >> s >> t;
    while (m -- )
    {
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(g[a][b], c);
    }
    dijkstra();
    cout << dist[t] << endl;
    return 0;
}

(堆优化dijkstra) O((n+m)logm)

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

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;
const int N = 2510, M = 6210 << 1;

int n, m, s, t;
int h[N], e[M], ne[M], w[M], idx;
int dist[N];
bool st[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    memset(st, 0, sizeof st);
    priority_queue<PII, vector<PII>, greater<>> heap;
    heap.push({0, s});
    dist[s] = 0;

    while (!heap.empty())
    {
        PII t = heap.top();
        heap.pop();

        st[t.y] = true;

        for (int i = h[t.y]; ~i; i = ne[i])
        {
            int j = e[i];
            if (dist[j] > dist[t.y] + w[i])
            {
                dist[j] = dist[t.y] + w[i];
                heap.push({dist[j], j});
            }
        }
    }
}
int main()
{
    memset(h, -1, sizeof h);
    cin >> n >> m >> s >> t;
    while (m -- )
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }
    dijkstra();
    cout << dist[t] << endl;
    return 0;
}

(spfa) O(m)


平均O(m),最坏O(nm)

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

using namespace std;

const int N=3000,M=20000;

int h[N],w[M],e[M],ne[M],idx;
bool ste[N];

void add(int a,int b,int c)
{
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}

int spfa(int st,int ed)
{
    int dist[M];
    memset(dist,0x3f,sizeof dist);
    queue<int> q;
    q.push(st);
    dist[st]=0;
    ste[st]=true;
    while(q.size())
    {
        int t=q.front();
        q.pop();
        ste[t]=false;
        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(!ste[j])
                {
                    ste[j]=true;
                    q.push(j);
                }
            }
        }
    }
    return dist[ed];
}

int main()
{
    int n,m,st,ed;
    cin>>n>>m>>st>>ed;
    memset(h,-1,sizeof h);
    for(int i=0;i<m;i++)
    {
        int a,b,w;
        scanf("%d%d%d",&a,&b,&w);
        add(a,b,w),add(b,a,w);
    }
    int t=spfa(st,ed);
    cout<< t <<endl;
    return 0;
}

三种代码运行时间对比:

 本题中 点数最大值为 2500,边数的最大值是 6200

由上到下是:spfa(m)          堆优化dij(mlogn)          朴素版dij(n^2)


2)信使

 题意概述:

 样例解释:

第一行输入 点数n        边数m

接下来的m行:

输入        a->b        的权重为 c

 思路解析:

这道题核心的地方就是知道了 起点(1号点)

但是不知道终点(不一定是N号点距离起点的距离最远)

那么题目中最关键的一句话是:直至所有岗哨都接到命令后,送信才算成功

(1)对于有岗哨不能接到命令的情况:处理完最短路后,仍然有dist[岗哨]==0x3f3f3f3f

(2)因为每一个岗哨都有充足的哨兵:可以类比为:

初始的1个人,每经历过一个岗哨,那么就会影分身向附近的没有走过的岗哨走去,那么再把每一个分身都当成初始的那一个人,就有经历了最大dist[岗哨],一定可以把所有岗哨走完

(仔细体会)

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

const int N=110;
int g[N][N];
int dist[N];
int n,m;
bool st[N];

int dijkstra()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    
    for(int i=1;i<=n;i++)
    {
        int t=-1;
        for(int j=1;j<=n;j++)
        {
            if(!st[j] && (t==-1 || dist[t]>dist[j]))
            {
                t=j;
            }
        }
        st[t]=true;
        
        for(int k=1;k<=n;k++)
        {
            dist[k]=min(dist[k],dist[t]+g[t][k]);
        }
    }
    //解释中的情况
    int res=-1;
    for(int i=1;i<=n;i++)
    {
        if(dist[i]==0x3f3f3f3f) return -1;
        res=max(res,dist[i]);
    }
    return res;
}

int main()
{
    cin>>n>>m;
    memset(g,0x3f,sizeof g);
    for(int i=1;i<=m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        if(a==b) continue;
        g[a][b]=g[b][a]=min(g[a][b],c);
    }

    cout<<dijkstra()<<endl;

    return 0;
}

3)香甜的黄油

 

核心思路:

(1)理解题意

<1>要求的是所有 奶牛到 指定 牧场的最小距离之和--->>其实已经有"多源最短路那味了"

(2)核心

<1>最短路枚举所有 牧场,然后求所有 奶牛到该牧场的最小距离,将所有奶牛到牧场的距离都加到 一个数组res[该牧场]中

·<2>然后枚举所有牧场,用ans记录 最小的res[牧场]

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

const int N = 810, M = 3000, INF = 0x3f3f3f3f;

int n, p, m;
int id[N];
int h[N], e[M], w[M], ne[M], idx;
int dist[N], q[N], res[N];
bool st[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

void spfa(int start)
{
    memset(dist, 0x3f, sizeof dist);
    dist[start] = 0;
    queue<int> q;
    //int hh = 0, tt = 1;
    q.push(start);
    st[start] = true;
    while (q.size())
    {
        int t = q.front();
        q.pop();
        //if (hh == N) hh = 0;
        st[t] = false;

        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[tt ++ ] = j;
                    q.push(j);
                    //if (tt == N) tt = 0;
                    st[j] = true;
                }
            }
        }
    }

    for (int i = 1; i <= p; i ++)
        if (dist[i] == INF) res[i] = INF;
        else res[i] += dist[i];
}

int main()
{
    cin >> n >> p >> m;
    for (int i = 0; i < n; i ++ ) cin >> id[i];

    memset(h, -1, sizeof h);
    for (int i = 0; i < m; i ++ )
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c), add(b, a, c);
    }

    for (int i = 0; i < n; i ++ ) 
        spfa(id[i]);

    int ans = INF;
    for (int i = 1; i <= p; i ++)
        ans = min(ans, res[i]);

    cout << ans << endl;

    return 0;
}

4)最小花费

 

 核心思路:

其实也就是把权重这个值  换成了 稍微数学一点而已--->>数学的东西还是手写舒服

注意:这里dist相当于累计的这一串        (100-z1)(100-z2)……

 

 

#include <iostream>
#include <queue>
#include <cstring>

using namespace std;

const int N = 2010 , M = 200010;

int e[M] , ne[M] , h[N] , idx;
double d[M] , w[M];
bool st[N];
int n , m;

void add(int a , int b , double c)
{
    e[idx] = b , ne[idx] = h[a] , w[idx] = c , h[a] = idx++;
}

double spfa(int a , int b)
{
    queue<int> q;
    q.push(a);
    d[a] = 1;

    while(q.size())
    {
        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] < d[t] * (1 - w[i]))//此时的 w[i]是 扣除的 比率 
            {                           //那么实际上所转的 钱是 d[]*(1-扣除的比率) == 转后的钱(看题解二)
                d[j] = d[t] * (1 - w[i]);
                if(!st[j])
                {
                    st[j] = true;
                    q.push(j);
                }
            }
        }
    }
    return d[b];
}

int main()
{
    cin >> n >> m;

    memset(h , -1 , sizeof h);
    while(m--)
    {
        int a , b ,  c;
        cin >> a >> b >> c;
        add(a , b , 1.0 * c / 100) , add(b , a , 1.0 * c / 100);
    }

    int a , b;
    cin >> a >> b;
    printf("%.8lf\n" , 100 / spfa(a , b));
    return 0;
}

5) 最优乘车

 

 核心思路:

题意概述:

要求的换乘的是最小次数

建图

两层循环,将一个结点后面的的所有点全部建为1

(1)如果,起点和终点都恰好 在这条路线上 那么 换乘的次数就是0,因此 输出答案需要-1

(2)如果,起点和终点可换成换乘一次即可,那么就有 换乘的次数为 1,与上同理

至于为什么将 边权建为 1,是为了方便换乘时好计算,只需在输出的时候-1即可

 注意:

由于不知道该路线一共有多少个 车站 

所以要用  stringstream 来 读入

 Floyd算法:

#include<iostream>
#include<cstring>
#include<sstream>
using namespace std;

const int N=510;
int n,m;
int s[N][N];

int main()
{
    cin>>m>>n;
    memset(s,0x3f,sizeof s);
    getchar();//吃回车

    while(m--)
    {
        string line;
        getline(cin,line);

        stringstream scin(line);
        int cnt=0,t,stop[N];
        while(scin>>t) stop[cnt++]=t;

        for(int i=0;i<cnt;i++)
            for(int j=i+1;j<cnt;j++)
                s[stop[i]][stop[j]]=1;
    }

    for(int k=1;k<=n;k++)
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
    s[i][j]=min(s[i][j],s[i][k]+s[k][j]);

    if(s[1][n]>=0x3f3f3f3f) cout<<"NO";
    else cout<<s[1][n]-1<<endl;

    return 0;
}

广度优先BFS

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

const int N=510;
int m,n;
bool g[N][N];
int dist[N];
int stop[N];
int q[N];

void bfs()
{
    memset(dist,0x3f,sizeof dist);
    queue<int> q;
    q.push(1);
    dist[1]=0;

    while(q.size())
    {
        int t=q.front();
        q.pop();

        for(int i=1;i<=n;i++)//扩张:n个车站
        {   //如果该车站在该条 路线存在  且 换成次数取小的
            if(g[t][i] && dist[i]>dist[t]+1)
            {
                dist[i]=dist[t]+1;
                q.push(i);
            }
        }
    }
}

int main()
{
    cin>>m>>n;
    
    string line;//吃掉回车
    getline(cin,line);

    while(m--)
    {
        getline(cin,line);
        stringstream ssin(line);
        int cnt=0,p;//因为不知道 输入的车站个数
        while(ssin>>p) stop[cnt++]=p;
        for(int j=0;j<cnt;j++)
        {
            for(int k=j+1;k<cnt;k++)//将它之后的车站标 true
            {   //该路线存在的条件
                g[stop[j]][stop[k]]=true;
            }
        }
    }

    bfs();
    if(dist[n]==0x3f3f3f3f) puts("NO");
    else cout<<max(dist[n]-1,0)<<endl;

    return 0;
}

dijkstra

#include <iostream>
#include <sstream>
#include <memory.h>
#include <queue>

using namespace std;

const int N = 510;

int n, m;
int stop[N];
/*
3 7
6 7
4 7 3 6
2 1 3 5
输出样例:
2
*/
int g[N][N];
int dist[N];
bool st[N];

int dij()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    for (int i = 0; i < n - 1; i++) {
        int t = -1;
        for (int j = 1; j <= n; j++) {
            if (!st[j] && (t == -1 || dist[t] > dist[j])) {
                t = j;
            }
        }

        for (int j = 1; j <= n; j++) {
            dist[j] = min(dist[j], dist[t] + g[t][j]);
        }

        st[t] = true;
    }

    return dist[n];
}


int main()
{
    cin >> m >> n;
    memset(g, 0x3f, sizeof g);
    string line;
    getline(cin, line);
    while (m--) {
        getline(cin, line);
        stringstream ssin(line);
        int cnt = 0, p;
        while (ssin >> p) stop[cnt++] = p;

        //每个汽车的前站达到后站的距离都为1 因为是同一辆车 未换车
        for (int i = 0; i < cnt; i++) {
            for (int j = i + 1; j < cnt; j++) {
                g[stop[i]][stop[j]] = 1;
            }
        }
    }

    int ret = dij();


    if ( ret > 0x3f3f3f3f/2) cout << "NO" << endl;
    else cout << ret-1 << endl;

    return 0;
}



作者:itdef
链接:https://www.acwing.com/solution/content/15468/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

6)昂贵的聘礼

903. 昂贵的聘礼 - AcWing题库

 

 

 核心思路:

<1>构造一个虚拟点0,那么就是求0-->>1 的所花金币的最短路

<2>关于怎么建图,我推荐根据样例自己在纸上画

<3>扩展边的时候额外+一个判断等级差距的条件即可

<4>剩下的就是模拟了,这个在注释已经非常详细了~~~

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

const int N=110;
const int 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=0;i<=n;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);//初始化所有边权为 INF
    for(int i=1;i<=n;i++) w[i][i]=0;//防止自环

    for(int i=1;i<=n;i++)//n个物品的描述
    {
        int price,cnt;
        cin>>price>>level[i]>>cnt;//直接换的物品的价格 主人的地位等级 替代品的总数
        w[0][i]=min(price,w[0][i]);//直接换的物品价格 -->> id:0 的虚拟点 -->> i的所需金币
        while(cnt--)//替代品
        {
            int id,cost;
            cin>>id>>cost;//替代品的编号 和 价格
            w[id][i]=min(w[id][i],cost);//从 id -> i 所需 的金币数
        }
    }

    int res=INF;
    //从等级1的 下限 开始 枚举(因为1点的是终点)
    for(int i=level[1]-m;i<=level[1];i++) res=min(res,dijkstra(i,i+m));

    cout<<res<<endl;
    
    return 0;
}

7)新年好

 

 

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

#define x first
#define y second
const int N=50010,M=2e5+10;
const int INF=0x3f3f3f3f;
typedef pair<int,int> PII;

int n,m;
int source[6];
bool st[N];
int dist[6][N];
int h[N],e[M],ne[M],w[M],idx;

void add(int a,int b,int c)
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}

void dijkstra(int start,int dist[])
{
    memset(dist,0x3f,4*N);//只是初始化 第 i 行 的数据(数组名做形参不能用 sizeof)
    memset(st,0,sizeof st);
    dist[start]=0;
    priority_queue<PII,vector<PII>,greater<PII>> heap;

    heap.push({0,start});//距离 点
    while(heap.size())
    {
        auto t=heap.top();
        heap.pop();

        int ver=t.y;
        int distance=t.x;
        if(st[ver]) continue;
        st[ver]=true;

        for(int i=h[ver];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>distance+w[i])
            {
                dist[j]=distance+w[i];
                heap.push({dist[j],j});
            }
        }
    }
}

// 枚举每种拜访次序,求出最小距离
// 拜访了u个人,自己是第1个人;当前起点是source[start],当前走过的距离是distance
int dfs(int u,int start,int distance)
{
    if(u==6) return distance;//u==6表示 拜访完5个亲戚,此时返回最短路

    int res=INF;
    for(int i=1;i<=5;i++)
    {
        if(!st[i])
        {
            int next=source[i];//走亲戚i
            st[i]=true;
            res=min(res,dfs(u+1,i,dist[start][next]+distance));
            st[i]=false;
        }
    }
    return res;
}

int main()
{
    cin>>n>>m;//n个车站 m条路线
    memset(h,-1,sizeof h);
    source[0]=1;//佳佳家所在的车站编号
    for(int i=1;i<=5;i++) cin>>source[i];//5个亲戚所在车站的编号
    while(m--)
    {
        int x,y,t;
        cin>>x>>y>>t;
        add(x,y,t),add(y,x,t);//无向图
    }
    //6个点,分别求最短路
    for(int i=0;i<6;i++) dijkstra(source[i],dist[i]);//只是初始化 第 i 行 的数据
    /*
    1. 共有6个人,起点是自己:第1个人
    2. 当前是source[0]:佳佳
    3. 当前走过的距离是0
    */
    memset(st,0,sizeof st);
    printf("%d\n",dfs(1,0,0));

    return 0;
}

8)通信线路

 

思路解析:AcWing 340. 通信线路 - AcWing

#include <iostream>
#include <cstring>
#include <deque>
#include <algorithm>

using namespace std;

const int N = 1010 , M = 20010;

int e[M] , ne[M] , w[M] , h[N] , idx;
int dist[N];
int n , m , k;
bool st[N];

void add(int a , int b , int c)
{
    e[idx] = b , ne[idx] = h[a] , w[idx] = c , h[a] = idx++;
}

bool check(int bound)
{
    memset(dist , 0x3f , sizeof dist);
    memset(st , 0 , sizeof st);

    dist[1] = 0;
    deque<int> q;
    q.push_back(1);

    while(q.size())
    {
        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] , x = w[i] > bound;
            if(dist[j] > dist[t] + x)
            {
                dist[j] = dist[t] + x;
                if(x) q.push_back(j);
                else q.push_front(j);
            }
        }
    }
    return dist[n] <= k;
}

int main()
{
    cin >> n >> m >> k;

    memset(h , -1 , sizeof h);
    while(m--)
    {
        int a , b , c;
        cin >> a >> b >> c;
        add(a , b , c) , add(b , a , c);
    }

    int l = 0 , r = 1e6 + 1;
    while(l < r)
    {
        int mid = l + r >> 1;
        if(check(mid)) r = mid;
        else l = mid + 1;
    }

    if(r == 1e6 + 1) r = -1;
    cout << r << endl;
    return 0;
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值