第三章 图论 No.1单源最短路及其综合应用

这篇具有很好参考价值的文章主要介绍了第三章 图论 No.1单源最短路及其综合应用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

做乘法的最短路时,若权值>=0,只能用spfa来做,相等于加法中的负权边

1129. 热浪

1129. 热浪 - AcWing题库
第三章 图论 No.1单源最短路及其综合应用,AcWing算法提高课 课程记录,图论,算法

单源最短路,稀疏图,用堆优化Dijkstra即可,就是板子套了个背景

// 稀疏图,用堆优化Dijkstra存储
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;

const int N = 2510, M = 2 * 6210;
typedef pair<int, int> PII;
priority_queue<PII, vector<PII>, greater<PII>> q;
int h[N], e[M], ne[M], w[M], idx = 1;
int n, m, start, ed;
int dis[N];
bool st[N];

void add(int x, int y, int d)
{
    e[idx] = y, ne[idx] = h[x], w[idx] = d, h[x] = idx ++ ;
}

void dijkstra()
{
    memset(dis, 0x3f, sizeof(dis));
    dis[start] = 0;
    q.push({0, start});
    while (q.size())
    {
        auto t = q.top(); q.pop();
        int x = t.second, d = t.first;
        if (st[x]) continue;
        st[x] = true;
        
        for (int i = h[x]; i != -1; i = ne[i])
        {
            int y = e[i];
            if (dis[y] > d + w[i])
            {
                dis[y] = d + w[i];
                q.push({dis[y], y});
            }
        }
    }
}

int main()
{
    memset(h, -1, sizeof(h));
    scanf("%d%d%d%d", &n, &m, &start, &ed);
    int x, y, d;
    for (int i = 1; i <= m; ++ i )
    {
        scanf("%d%d%d", &x, &y, &d);
        add(x, y, d), add(y, x, d);
    }
    
    dijkstra();
    printf("%d\n", dis[ed]);
    return 0;
}

debug:由于是无向图,边的数量要开两倍。但是w[N]没改,debug了很久
所以e[M], ne[M], w[M],只有h[N],其他的数组存储的都是边


1128. 信使

1128. 信使 - AcWing题库
第三章 图论 No.1单源最短路及其综合应用,AcWing算法提高课 课程记录,图论,算法

单源最短路,稀疏图,用堆优化Dijkstra即可,最后统计所有dis的最大值并返回即可

// 单源最短路,最后将第1个点到其他点的距离累加即可
// 稀疏图,依然是堆优化的dijkstra
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;

typedef pair<int, int> PII;
const int N = 110, M = 410;
int h[N], e[M], ne[M], w[M], idx = 1;
priority_queue<PII, vector<PII>, greater<PII>> q;
int n, m;
bool st[N]; int dis[N];

void add(int x, int y, int d)
{
    e[idx] = y, ne[idx] = h[x], w[idx] = d, h[x] = idx ++ ;
}

void dijkstra()
{
    memset(dis, 0x3f, sizeof(dis));
    dis[1] = 0;
    q.push({0, 1});
    while (q.size())
    {
        auto t = q.top(); q.pop();
        int x = t.second, d = t.first;
        
        if (st[x]) continue;
        st[x] = true;
        for (int i = h[x]; i != -1; i = ne[i]) 
        {
            int y = e[i];
            if (dis[y] > d + w[i])
            {
                dis[y] = d + w[i];
                q.push({dis[y], y});
            }
        }
    }
}

int main()
{
    memset(h, -1, sizeof(h));
    scanf("%d%d", &n, &m);
    int x, y, d;
    for (int i = 1; i <= m; ++ i )
    {
        scanf("%d%d%d", &x, &y, &d);
        add(x, y, d), add(y, x, d);
    }
    
    dijkstra();
    
    int res = 0;
    bool flag = true;
    for (int i = 2; i <= n; ++ i )
    {
        if (dis[i] == 0x3f3f3f3f) 
        {
            flag = false;
            break;
        }
        res = max(res, dis[i]);
    }
    if (flag) printf("%d\n", res);
    else puts("-1");

    return 0;
}

debug:%d打成了5d,乐,竟然能编译通过
判断某个点的最短距离是否已经确定,打成了if(dis[x]); dis[x] = true,这也debug了半天


1127. 香甜的黄油

1127. 香甜的黄油 - AcWing题库
第三章 图论 No.1单源最短路及其综合应用,AcWing算法提高课 课程记录,图论,算法

// 多源汇最短路,由于flody可能超时,所以用单源最短路求解
// 这里用spfa
#include <iostream>
#include <cstring>
using namespace std;

const int N = 810, M = 3000;
int h[N], e[M], ne[M], w[M], idx = 1;
int dis[N]; bool st[N];
int s, n, m;
int q[N];
int id[N];

void add(int x, int y, int d)
{
    e[idx] = y, ne[idx] = h[x], w[idx] = d, h[x] = idx ++ ;
}

void spfa(int k)
{
    memset(dis, 0x3f, sizeof(dis));
    int hh = 0, tt = 1;
    dis[k] = 0;
    q[tt ++ ] = k, st[k] = true;
    while (tt != hh)
    {
        int x = q[hh ++ ];
        if (hh == N) hh = 0;
        st[x] = false;
        for (int i = h[x]; i != -1; i = ne[i])
        {
            int y = e[i];
            if (dis[y] > dis[x] + w[i])
            {
                dis[y] = dis[x] + w[i];
                if (!st[y]) 
                {
                    q[tt ++ ] = y, st[y] = true;
                    if (tt == N) tt = 0;
                }
            }
        }
    }
}

int main()
{
    memset(h, -1, sizeof(h));
    scanf("%d%d%d", &s, &n, &m);
    for (int i = 1; i <= s; ++ i ) scanf("%d", &id[i]);
    
    int x, y, d;
    for (int i = 1; i <= m; ++ i )
    {
        scanf("%d%d%d", &x, &y, &d);
        add(x, y, d), add(y, x, d);
    }
    
    int res = 0x3f3f3f3f;
    for (int i = 1; i <= n; ++ i )
    {
        spfa(i);
        int sum = 0; bool flag = true;
        for (int j = 1; j <= s; ++ j ) 
        {
            if (dis[id[j]] == 0x3f3f3f3f)
            {
                flag = false;
                break;
            }
            sum += dis[id[j]];
        }
        if (flag) res = min(res, sum);
    }
    
    printf("%d\n", res);
    return 0;
}

用自己写的循环队列代替queue,时间大概快了一倍
需要注意的是,循环队列的hh和tt从0开始使用,并且都是后置++,++后还要维护循环性质

以下是用queue实现的版本

// 多源汇最短路,由于flody可能超时,所以用单源最短路求解
// 这里用spfa
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;

typedef long long LL;
const int N = 810, M = 1500 * 2;
int h[N], e[M], ne[M], w[M], idx = 1;
int dis[N]; bool st[N];
int s, n, m;
queue<int> q;
int id[N];

void add(int x, int y, int d)
{
    e[idx] = y, ne[idx] = h[x], w[idx] = d, h[x] = idx ++ ;
}

void spfa(int k)
{
    memset(dis, 0x3f, sizeof(dis));
    dis[k] = 0;
    q.push(k), st[k] = true;
    while (q.size())
    {
        int x = q.front(); q.pop();
        st[x] = false;
        for (int i = h[x]; i != -1; i = ne[i])
        {
            int y = e[i];
            if (dis[y] > dis[x] + w[i])
            {
                dis[y] = dis[x] + w[i];
                if (!st[y]) q.push(y), st[y] = true;
            }
        }
    }
}

int main()
{
    memset(h, -1, sizeof(h));
    scanf("%d%d%d", &s, &n, &m);
    int t;
    for (int i = 1; i <= s; ++ i ) scanf("%d", &id[i]);
    
    int x, y, d;
    for (int i = 1; i <= m; ++ i )
    {
        scanf("%d%d%d", &x, &y, &d);
        add(x, y, d), add(y, x, d);
    }
    
    LL res = 1e18;
    for (int i = 1; i <= n; ++ i )
    {
        spfa(i);
        LL sum = 0; bool flag = true;
        for (int i = 1; i <= s; ++ i )
        {
            if (dis[id[i]] == 0x3f3f3f3f) 
            {
                flag = false;
                break;
            }
            sum += dis[id[i]];
        }
        if (flag) res = min(res, sum);
    }
    
    printf("%lld\n", res);
    return 0;
}

1126. 最小花费

1126. 最小花费 - AcWing题库
第三章 图论 No.1单源最短路及其综合应用,AcWing算法提高课 课程记录,图论,算法

转账金额A,当手续费的比例为z时,只能转账A(1 - z)
若每条边的权值为实际转账的金额比例,那么从起点到终点,这个比例越大,花费的初始金额就越少,所以这题需要找一条从起点到终点比例最大的路径
即从起点到终点经过的所有边的比例相乘最大,由于比例的大小为 0 < z < = 1 0 < z <= 1 0<z<=1,所以这里初始化dis数组以及邻接矩阵为全0
接着用spfa或者dijkstra求“最短路”即可

// 同样的单源最短路,spfa解决

#include <iostream>
using namespace std;

const int N = 2010;
double g[N][N];
int n, m, start, ed;
double dis[N]; bool st[N];
int q[N], tt, hh;

void spfa()
{
    dis[start] = 1.0;
    q[tt ++ ] = start, st[start] = true;
    while (tt != hh)
    {
        int x = q[hh ++ ];
        if (hh == N) hh = 0;
        st[x] = false;
        for (int j = 1; j <= n; ++ j )
        {
            if (dis[j] < dis[x] * g[x][j])
            {
                dis[j] = dis[x] * g[x][j];
                if (!st[j]) 
                {
                    q[tt ++ ] = j, st[j] = true;
                    if (tt == N) tt = 0;
                }
            }
        }
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    int x, y, z;
    for (int i = 1; i <= m; ++ i )
    {
        scanf("%d%d%d", &x, &y, &z);
        g[x][y] = g[y][x] = 1 - (z / 100.0);
    }
    scanf("%d%d", &start, &ed);
    
    spfa();
    
    printf("%.8lf\n", 100 / dis[ed]);
    return 0;
}

920. 最优乘车

920. 最优乘车 - AcWing题库
第三章 图论 No.1单源最短路及其综合应用,AcWing算法提高课 课程记录,图论,算法

没之前的题目裸,需要一些思考
虽然题目没有给定边的权重,但是给定了公交路线,可知公交路线 a a a中, a i a_i ai a j a_j aj的权重为1,表示从i地到j地需要的乘车次数,当如 a i a_i ai a a a中的下标要出现在 a j a_j aj之前,这是一张有向图
所以,根据题目给定的公交路线,找出图中所有的有向边,最后用单源最短路就行了
由于边的权值为1,甚至用bfs也行

// 一条路线之间的点可达,存在有向边,线路中有n个点,存在Cn2条边
// 边的权值为1,表示需要乘车的次数,由于权值为1,所以可以用bfs
#include <iostream>
#include <sstream>
#include <string>
#include <cstring>
using namespace std;

const int N = 510, M = 11;;
int a[N], g[N][N], q[N];
int dis[N];
int n, m, tt, hh;

int bfs()
{
    dis[1] = 0;
    q[tt ++ ] = 1;
    while (tt != hh)
    {
        int x = q[hh ++ ];
        if (hh == N) hh = 0;
        
        for (int y = 1; y <= n; ++ y )
        {
            if (y != x && !dis[y] && g[x][y]) 
            {
                dis[y] = dis[x] + 1;
                if (y == n) return dis[y];
                q[tt ++ ] = y;
            }
        }
    }
    return 0;
}

int main()
{
    scanf("%d%d", &m, &n);
    string line;
    getline(cin, line);
    for (int i = 0; i < m; ++ i )
    {
        getline(cin, line);
        stringstream s(line);
        int idx = 0, p;
        while (s >> p) a[idx ++ ] = p;
        
        for (int j = 0; j < idx; ++ j )
            for (int k = j + 1; k < idx; ++ k )
                g[a[j]][a[k]] = 1;
    }
    
    int t = bfs();
    if (t == 0) puts("NO");
    else printf("%d\n", t - 1);
    
    return 0;
}

903. 昂贵的聘礼

903. 昂贵的聘礼 - AcWing题库
第三章 图论 No.1单源最短路及其综合应用,AcWing算法提高课 课程记录,图论,算法

以下是我的思路,我只是记录一下不建议看
要获得某个物品,有两种方式:1. 直接购买 2. 在拥有替代品的前提下,以优惠价格购买
此外无法找出其他方法,需要考虑的是:以优惠价格购买时,拥有替代品的代价是什么?
若从正面考虑,假设酋长的10000金币是一个物品,现可以直接购买或者使用替代品。若使用替代品以更低的价格购买,那么购买替代品的价格是多少?同样,购买替代品也有两种方式,是直接购买省钱还是用替代品的替代品购买省钱。这是一个无止尽的过程,什么时候会停止?物品没有替代品时停止,这就意味着我们要递归到最底部后,在向上回溯的过程中,才能计算两种方式中哪种最省钱
这是直接的思路,可能能做出来吧,没试过


正解:
将酋长的10000金币看成物品,并且是有向图的终点。物品之间有什么关系?从一个点走到另一个点,前提是当前点是下一点的替代品,那么从当前点到下一点的代价就是用当前物品购买下一物品的优惠价格
所以边的权重为优惠价格,当前除了以优惠价格购买,还能直接购买。直接购买时,当前点的含义不再是一个物品,所以这里的点是一个虚拟点,到其他点的权重为直接购买的价格

若当前已经购买了物品,走向下一物品时,直接购买的价格肯定比使用替代品高,所以不用设置已经购买物品时,再直接购买其他物品的边。这样的边没有意义
由于存在一些物品无法使用替代品购买,所以只要设置手上没有物品时,直接购买物品的边
第三章 图论 No.1单源最短路及其综合应用,AcWing算法提高课 课程记录,图论,算法
虚拟点为上图的s
起点为s,终点为1号物品(酋长的10000金币),跑一个最短路即可
为什么起点是s?存在一些只能直接购买的物品

最后考虑等级限制,等级差距不超过m的人可以交换物品,由于题目数据量小,可以直接暴力枚举。假设酋长的等级为a,那么需要枚举的区间为 [ a − m , a + m ] [a-m, a+m] [am,a+m],枚举这个区间中长度为m的区间,每次枚举跑一次最短路即可

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

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

int dijkstra(int l, int r)
{
    memset(dis, 0x3f, sizeof(dis));
    memset(st, false, sizeof(st));
    dis[0] = 0;
    for (int i = 0; i <= n; ++ i )
    {
        int t = -1;
        for (int j = 0; j <= n; ++ j)
            if (!st[j] && (t == -1 || dis[j] < dis[t])) t = j;
        st[t] = true;
        for (int j = 1; j <= n; ++ j )
            if (l <= level[j] && level[j] <= r)
                dis[j] = min(dis[j], dis[t] + g[t][j]);
    }
    return dis[1];
}

int main()
{
    memset(g, 0x3f, sizeof(g));
    scanf("%d%d", &m, &n);
    int p, x, t, v;
    for (int i = 1; i <= n; ++ i ) g[i][i] = 0;
    for (int i = 1; i <= n; ++ i )
    {
        scanf("%d%d%d", &p, &level[i] , &x);
        g[0][i] = min(g[0][i], p);
        while ( x -- )
        {
            scanf("%d%d", &t, &v);
            g[t][i] = min(g[t][i], v);
        }
    }
    
    int res = 0x3f3f3f3f;
    for (int i = level[1] - m; i <= level[1]; ++ i )
    {
        int t = dijkstra(i, i + m);
        res = min(t, res);
    }
    printf("%d\n", res);
    return 0;
}

1135. 新年好

1135. 新年好 - AcWing题库
第三章 图论 No.1单源最短路及其综合应用,AcWing算法提高课 课程记录,图论,算法

先用堆优dijkstra跑单源最短路,一共是6个源点,分别是家所在的点以及亲戚所在的点,保存最短距离
然后dfs爆搜,以1号点为起点,剩下五个点的全排列为搜索顺序,记录全排列中路径的最短距离

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

const int N = 5e5 + 10, M = 2e5 + 10;
const int INF = 1e9;
int h[N], e[M], ne[M], w[M], idx = 1;
int dis[6][N]; bool st[N];
int tar[10];
int n, m;
typedef pair<int, int> PII;
priority_queue<PII, vector<PII>, greater<PII>> q;

void add(int x, int y, int d)
{
    e[idx] = y, ne[idx] = h[x], w[idx] = d, h[x] = idx ++ ;    
}

void dijkstra(int start, int si) // si为start在dis数组中的一维下标
{
    memset(st, false, sizeof(st));
    dis[si][start] = 0;
    q.push({ 0, start });
    while (q.size())
    {
        auto t = q.top(); q.pop();
        int x = t.second, d = t.first;
        if (st[x]) continue;
        st[x] = true;
        for (int i = h[x]; i != -1; i = ne[i])
        {
            int y = e[i];
            if (dis[si][y] > d + w[i])
            {
                dis[si][y] = d + w[i];
                q.push({ dis[si][y], y});
            }
        }
    }
}

int dfs(int cnt, int si, int d) // cnt为已经拜访的亲戚数量,si为当前所在点在dis数组中的一维下标,d为递达当前点的“距离”
{
    if (cnt == 5)  return d;
    int res = INF;
    for (int i = 1; i <= 5; ++ i )
    {
        if (!st[i])
        {
            int y = tar[i]; // 要访问的下一个点在图中的编号
            st[i] = true;
            res = min(res, dfs(cnt + 1, i, d + dis[si][y]));
            st[i] = false;
        }
    }
    return res;
}

int main()
{
    memset(h, -1, sizeof(h));
    memset(dis, 0x3f, sizeof(dis));
    scanf("%d%d", &n, &m);
    for ( int i = 1; i <= 5; ++ i ) scanf("%d", &tar[i]);
    int x, y, t;
    while ( m -- )
    {
        scanf("%d%d%d", &x, &y, &t);
        add(x, y, t), add(y, x, t);
    }
    
    dijkstra(1, 0);
    for (int i = 1; i <= 5; ++ i ) dijkstra(tar[i], i);
    
    memset(st, false, sizeof(st));
    printf("%d\n", dfs(0, 0, 0));
    return 0;
}

debug:双向边,M又少开一倍,但是TLE,以后M都开两倍吧,这太难调试了
dfs最后要返回res,因为min要使用dfs的返回值


340. 通信线路

340. 通信线路 - AcWing题库
第三章 图论 No.1单源最短路及其综合应用,AcWing算法提高课 课程记录,图论,算法

分析题意:对于每条1~n的路径,我们只要支付第k+1大的边的金额即可。若边的数量少于k+1,那么支付金额为0
所以本题的最短路,短在第k+1大的边权中,最小的那个。所有最大中找最小,自然地想到二分。二分边权,得到ans,使得ans是所有1~n路径中,第k+1大的权值中最小的那个

二分的范围: [ 0 , 1 e 6 + 1 ] [0, 1e6+1] [0,1e6+1],权值最小值-1~权值最大值+1
check:分析ans的性质,所有1~n的路径中,第k+1大的边权中最小值为ans
说明至少存在一条路径,经过了k条边权大于ans的边,即and是这条路径中第k+1大的边
那么其他路径就一定经过了多于k条权值大于ans的边
由此得到check的检查逻辑:根据参数x,判断1~n的所有路径中,是否存在一条路径,经过了小于等于k条的权值大于x的边。若存在返回true,否则返回false
试想一下,x越大,check的性质越容易满足,x越小,性质越难满足

两种特殊情况:ans为0,说明1~n的所有路径中,至少存在一条路径,经过了k条权值大于0的边,那么第k+1条边的权值为0,需要支付金额为0
ans为 1 e 6 + 1 1e6+1 1e6+1,说明1n的所有路径中,至少存在一条路径,经过了k条权值大于$1e6+1$的边,由于边权最大为$1e6$,所以这样的路径是不存在的,即1n之间不连通

如何得知1~n的所有路径中,经过了几条权值大于x的边?
对于权值大于x的边,将其权值设置为1,否则设置为0,跑一遍最短路,就能知道1~n的最短路径经过了几条权值大于x的边
对于权值只有0和1的最短路问题,可以用双端队列bfs

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

const int N = 1010, M = 20010;
int n, p, k;
int h[N], e[M], ne[M], w[M], idx = 1;
int dis[N]; bool st[N];
deque<int> q;

void add(int x, int y, int d)
{
    e[idx] = y, ne[idx] = h[x], w[idx] = d, h[x] = idx ++ ; 
}

bool check(int bound)
{
    memset(dis, 0x3f, sizeof(dis));
    memset(st, false, sizeof(st));
    dis[1] = 0;
    q.push_back(1);
    while (q.size())
    {
        int x = q.front(); q.pop_front();
        if (st[x]) continue;
        st[x] = true;
        for (int i = h[x]; i != -1; i = ne[i])
        {
            int y = e[i], d = w[i] > bound;
            if (dis[y] > dis[x] + d)
            {
                dis[y] = dis[x] + d;
                if (d) q.push_back(y);
                else q.push_front(y);
            }
        }
    }
    return dis[n] <= k;
}

int main()
{
    memset(h, -1, sizeof(h));
    scanf("%d%d%d", &n, &p, &k);
    int x, y, d;
    while ( p -- )
    {
        scanf("%d%d%d", &x, &y, &d);
        add(x, y, d), add(y, x, d);
    }
    
    int l = 0, r = 1e6 + 1;
    while ( l < r )
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    
    if (l == 1e6 + 1) puts("-1");
    else printf("%d\n", l);
    return 0;
}

342. 道路与航线

342. 道路与航线 - AcWing题库
第三章 图论 No.1单源最短路及其综合应用,AcWing算法提高课 课程记录,图论,算法

直接用spfa会被卡两个数据
根据题意:道路,双向边,存在环,正权;航线:单向边,无环,负权
其中关于是否有环以及权值的正负是选择算法的关键
若图中没有负权边,直接用堆d
若图中无环,直接按照拓扑序遍历所有点就能确定最短路

结合以上两者,可以得到以下算法:
将所有点根据道路的连通性分成多个连通块,每个连通块之间不连通
连通每个连通块的是航线,单向边。读取所有的航线后,按照拓扑序遍历每个连通块,对于每个连通块,跑堆d求最短路

用id数组表示每个点所属的连通块编号,用二维数组blocks存储每个连通块中点的编号
读入所有道路后,dfs可以遍历连通块中的所有点,维护出id和blocks数组信息
读入所有航线,维护出in数组,计算所有点的入度后按照拓扑序跑堆d

// 道路是双向的,航线单向,可能是负数也可能是正数
// 并且图中保证不会存在环路
// 直接spfa?
#include <iostream>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;

typedef pair<int, int> PII;
const int N = 3e4, M = 2e5;
int h[N], e[M], ne[M], w[M], idx = 1;
int n, p, r, s;
int dis[N]; bool st[N];
int id[N], cnt, in[N];
vector<int> blocks[N];
queue<int> q;

void add(int x, int y, int d)
{
    e[idx] = y, ne[idx] = h[x], w[idx] = d, h[x] = idx ++ ;
}

void dfs(int x)
{
    id[x] = cnt;
    blocks[cnt].push_back(x);
    for (int i = h[x]; i != -1; i = ne[i])
    {
        int y = e[i];
        if (!id[y]) dfs(y);
    }
}

void dijkstra(int k)
{
    priority_queue<PII, vector<PII>, greater<PII>> hp;
    for (auto x : blocks[k]) hp.push({ dis[x], x });
    
    while (hp.size())
    {
        auto t = hp.top(); hp.pop();
        int x = t.second, d = t.first;
        if (st[x]) continue;
        st[x] = true;
        for (int i = h[x]; i != -1; i = ne[i])
        {
            int y = e[i];
            if (id[x] != id[y] && -- in[id[y]] == 0) q.push(id[y]);
            if (dis[y] > d + w[i])
            {
                dis[y] = d + w[i];
                if (id[x] == id[y]) hp.push( {dis[y], y });
            }
        }
    }
}

void topsort()
{
    memset(dis, 0x3f, sizeof(dis));
    dis[s] = 0;
    for (int i = 1; i <= cnt; ++ i )
        if (!in[i]) q.push(i);
    
    while (q.size())
    {
        int x = q.front(); q.pop(); // 连通块id
        dijkstra(x);
    }
}

int main()
{
    memset(h, -1, sizeof(h));
    scanf("%d%d%d%d", &n, &r, &p, &s);
    int x, y, d;
    for (int i = 0; i < r; ++ i )
    {
        scanf("%d%d%d", &x, &y, &d);
        add(x, y, d), add(y, x, d);
    }
    
    for (int i = 1; i <= n; ++ i )
    {
        if (!id[i]) 
        {
            cnt ++ ;
            dfs(i); // 维护出i所在的连通块
        }
    }
    
    for (int i = 0; i < p; ++ i )
    {
        scanf("%d%d%d", &x, &y, &d);
        add(x, y, d);
        in[id[y]] ++ ;
    }
    
    topsort();
    
    for (int i = 1; i <= n; ++ i ) 
    {
        if (dis[i] >= 0x3f3f3f3f / 2) puts("NO PATH");
        else printf("%d\n", dis[i]);
    }
    return 0;
}

341. 最优贸易

341. 最优贸易 - AcWing题库
第三章 图论 No.1单源最短路及其综合应用,AcWing算法提高课 课程记录,图论,算法

状态表示:
集合:所有从1~n的路径,用分界点将这些路径一分为二
属性:在分界点之前(包括分界点)买入 - 在分界点(包括分界点)之后能卖出的最大价值,即最大卖出价值 - 最小买入价值
f ( i ) f(i) f(i)表示以i号点为分界点的1~n路径中,可以赚取的最大价值

状态划分:分界点从1~n,此时划分的子集不遗漏地组成了整个集合(虽然有重复,但是题目要求最优解,重复的情况不影响最优解)

如何求路径中的前半段最小值 d m i n ( k ) dmin(k) dmin(k)
若与k直接相连的点有t个,那么 d m i n ( k ) = m i n ( d m i n ( s 1 ) , d m i n ( s 2 ) , . . . d m i n ( s t ) , w k ) dmin(k) = min(dmin(s_1), dmin(s_2), ... dmin(s_t), w_k) dmin(k)=min(dmin(s1),dmin(s2),...dmin(st),wk)
走到k的最小值就等于从所有走到与k直接相连的点的最小值以及自己的权值中的最小值
由于这些状态依赖可能存在环,即当dp推导出的状态不是最优解,所以这里用最短路求这些状态
最短路用dijkstra或者spfa,是否能用dijkstra?由于在最短路问题中,当前点的最短路径由与之相连的所有点的最短路径累加上自己的权值确定,并且权值只能是正的
根据这个性质,已经确定最短路的点在之后的更新中不可能出现更短的路径

虽然这题的边权为正,但是这题的状态不是由之前的状态累加,而是从之前的状态中取min或者max,这就导致dijkstra每次的更新无法求得最优解,需要后续的再次更新,此时的时间复杂度无法保证,这是最关键的,所以不使用dijkstra

那spfa呢?spfa是bellman-ford的优化,bellman根据边的数量进行更新,每次更新所有边
所以bellman能够保证最短路的边数,即时间复杂度是确定的,所以可以使用sfpa

用sfpa跑出dmin和dmax数组的信息,分别存储了从1到k的最小值,以及从k到n的最大值
由于从k到n的起点是变化的,但是终点不变,所以这里可以建一张反向图,以n为起点,更新dmax文章来源地址https://www.toymoban.com/news/detail-625960.html

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

const int N = 1e5 + 10, M = 2e6;
int w[N];
int hs[N], ht[N], e[M], ne[M], idx;
int dmin[N], dmax[N]; bool st[N];
int n, m;
int q[N];

void spfa(int h[], int dis[], int t)
{
    int tt = 0, hh = 0;
    if (t == 1)
    {
        memset(dis, 0x3f, sizeof(dmin));
        q[tt ++ ] = 1; st[1] = true;
        dis[1] = w[1];
    }
    else
    {
        memset(dis, -0x3f, sizeof(dmax));
        q[tt ++ ] = n; st[n] = true;
        dis[n] = w[n];
    }
    
    while (tt != hh)
    {
        int x = q[hh ++ ];
        if (hh == N) hh = 0;

        st[x] = false;
        for (int i = h[x]; i != -1; i = ne[i])
        {
            int y = e[i];
            if ((t == 1 && dis[y] > min(dis[x], w[y])) || (t == 2 && dis[y] < max(dis[x], w[y])))
            {
                if (t == 1) dis[y] = min(dis[x], w[y]);
                else dis[y] = max(dis[x], w[y]);
                if (!st[y]) 
                {
                    st[y] = true, q[tt ++ ] = y;
                    if (tt == N) tt = 0;
                }
            }
        }
    }
}

void add(int h[], int x, int y)
{
    e[idx] = y, ne[idx] = h[x], h[x] = idx ++ ;
}

int main()
{
    memset(hs, -1, sizeof(hs));
    memset(ht, -1, sizeof(ht));
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++ i ) scanf("%d", &w[i]);
    int x, y, t;
    while ( m -- )
    {
        scanf("%d%d%d", &x, &y, &t);
        add(hs, x, y), add(ht, y, x);
        if (t == 2) add(hs, y, x), add(ht, x, y);

    }
    
    spfa(hs, dmin, 1);
    spfa(ht, dmax, 2);
    
    int res = 0;
    for (int i = 1; i <= n; ++ i )
    {
        res = max(res, dmax[i] - dmin[i]);
    }
    
    printf("%d\n", res);
    
    return 0;
}

到了这里,关于第三章 图论 No.1单源最短路及其综合应用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【图论】单源最短路问题

    Dijkstra算法是一种单源最短路径算法,用于找出图中从一个源点到其他所有点的最短路径。该算法的原理是采用贪心策略,每次将距离源点最近的点加入到已确定最短路径的集合中,并更新其它节点的距离。具体实现过程如下: 初始化距离数组dist[],源点距离为0,其余点距离

    2024年02月13日
    浏览(39)
  • 【图论】单源最短路

    算法提高课笔记 最短路问题可以分为以下两类: 边权非负——朴素Dijkstra、堆优化Dijkstra 有负权边——Bellman-Ford、SPFA 热浪 原题链接 德克萨斯纯朴的民众们这个夏天正在遭受巨大的热浪!!! 他们的德克萨斯长角牛吃起来不错,可是它们并不是很擅长生产富含奶油的乳制品

    2024年02月14日
    浏览(43)
  • 【图论 单源最短路】100276. 最短路径中的边

    单源最短路 图论知识汇总 给你一个 n 个节点的无向带权图,节点编号为 0 到 n - 1 。图中总共有 m 条边,用二维数组 edges 表示,其中 edges[i] = [ai, bi, wi] 表示节点 ai 和 bi 之间有一条边权为 wi 的边。 对于节点 0 为出发点,节点 n - 1 为结束点的所有最短路,你需要返回一个长度

    2024年04月22日
    浏览(34)
  • 第三章 图论 No.13拓扑排序

    拓扑序和DAG有向无环图联系在一起,通常用于最短/长路的线性求解 裸题:1191. 家谱树 1191. 家谱树 - AcWing题库 差分约束+拓扑排序:1192. 奖金 1192. 奖金 - AcWing题库 由于图中所有边权都是正数,可以直接使用topsort求解差分约束问题 根据题意,要求一个最小值,使用最长路求解

    2024年02月12日
    浏览(45)
  • 算法提高-图论-单源最短路的综合应用

    多次dijkstra求每个点到其它点的最短距离, 此时相当于建好了一张图,每个点之间的最短距离都知道了,接下来dfs搜一下怎么走最短即可 一篇博客解释了为什么一个正向建图求最小值,反向建图求最大值 根本思想是保证1到n的买卖是连通的

    2024年02月11日
    浏览(75)
  • 算法提高-图论-单源最短路的扩展应用

    多源点单终点最短路建图: 创建虚拟源点(创建虚拟源点的时候以是spfa为例 可以在建图的时候建出来,也可以在spfa这直接入队,也是虚拟源点的意思) 反向建图变成单源点多终点,然后遍历终点的dist即可找出最短路 这题挺简单的就不详细说了,主要是第一次遇到计数问题

    2024年02月16日
    浏览(47)
  • 算法提高-图论-单源最短路的建图方式

    建图 找出一个牧场,它到其他牧场的距离之和最小 我是这么理解的,djsktra是一个贪心的思想,加法里面不能加负数我就不说了 求乘法最大值的时候为什么边权必须0-1,因为在乘法最大值里面有一个边权大于1的话那不就等价于求加法最小值的时候有一个边权为负数的么,d

    2024年02月08日
    浏览(45)
  • 第三章 图论 No.12欧拉回路与欧拉路径

    小学一笔画问题,每条边只经过一次 判断图是否存在欧拉回路:判断图是否连通(存在孤立边),再根据有向/无向具体判断 对于无向图来说, 欧拉路径 中,起点和终点的度数为奇数,中间点的度数为偶数 起点和终点:开始和结束时必须经过一条边,其余情况为:从一条边

    2024年02月12日
    浏览(41)
  • 图论算法基础:单源最短路径Dijkstra算法分析

    在 有向带权图 中给定一个起始顶点(源点),Dijkstra算法可以求出 所有其他顶点 到源点的最短路径,Dijkstra算法 不能用于同时含有正负权值的边的图 Source 顶点集合:已经确定 到源点的最短路径 的顶点就会加入 Source 集合中, Source 集合初始时只有源点 dist 数组:用于记录每个顶点到

    2024年02月11日
    浏览(44)
  • 【单源最短路 图论】882. 细分图中的可到达节点

    视频算法专题 单源最短路 图论 给你一个无向图(原始图),图中有 n 个节点,编号从 0 到 n - 1 。你决定将图中的每条边 细分 为一条节点链,每条边之间的新节点数各不相同。 图用由边组成的二维数组 edges 表示,其中 edges[i] = [ui, vi, cnti] 表示原始图中节点 ui 和 vi 之间存在

    2024年04月10日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包