题目链接
AcWing: https://www.acwing.com/problem/content/343/
洛谷: https://www.luogu.com.cn/problem/P1073
前置知识
存在负权的单源最短路和SPFA算法
思路分析
第一部分:对题目的分析——最多旅费的赚取
不难想到,我们希望在一条路径上,在一点上以尽可能低的价格买入,同时在另一点上以尽可能高的价格卖出,便可以使得我们所得到的旅费尽可能的大。
也就是说,如果设现在经过一点 k k k,我们则希望得出这样两个数组 d m i n [ k ] dmin[k] dmin[k] 和 d m a x [ k ] dmax[k] dmax[k],分别用以记录 1 1 1 至 k k k 的路径上的最小值和 k k k 至 n n n 的路径上的最大值, d m a x [ k ] − d m i n [ k ] dmax[k]-dmin[k] dmax[k]−dmin[k],便是经过该点时所能赚取到的最大差距,然后我们再枚举各点求出其最大值即可。
这个过程实际上类似于动态规划,不重不漏地考虑到各点的情况。
第二部分:代码的实现——最小和最大值的计算

首先一个前置知识就是,基于最短路算法的原理,最短路算法也可以用于求解最长路。
我们将整个游历的过程大致简化为这样,总之依题意,就是从点 1 1 1 游历 到 n n n 点。那么从点 1 1 1 游历到中间任意一点 k k k 的距离,符合固定起点(单源),多个终点 的单源最短路算法经典情况,可以使用最短路算法。但是从点 k k k 到点 n n n,则是固定终点,多个起点 的模式,如果我们一定要正向求解,那么就需要对每一个点 k k k 都做一遍到 n n n 的最长路,时间复杂度会很高,所以我们采用这样的优化,以 n n n 为起点反向建图,求解到中间各点 k k k 的最长路,这样也就等效于正向求解了。
接下来我们需要选用合适最短路算法。此处我们不能用 Dijkstra 算法,首先 Dijkstra 算法的原理,是在小根堆中取出当前距离最小的点来更新其他的领接点,在没有负权边的图中,如果后面仍然存在到达该点的路径,由于边权的非负性,也只会让距离不变或者更大,而不可能更小,因此在没有负权边的图中,Dijkstra 算法是可行的。但在这一题中,我们会发现实际上并没有所谓的边权,权值都是以点权的形式出现,而且并没有保证无环,这就意味着,如果使用了 Dijkstra 算法固定了某一点,在之后的路径中仍然可能遇到更小的点并且走回该点,这样就产生了负权边。
因此对于这一题,我们选用负权边的最短路算法 SPFA,而不是非负权边算法 Dijkstra。
第三部分:思路整合
我们再整合一下该题的思路:
1.
1.
1. 正向和反向建图,正向图用于求解
d
m
i
n
[
k
]
dmin[k]
dmin[k],反向图用于求解
d
m
a
x
[
k
]
dmax[k]
dmax[k];
2.
2.
2. 用两次SPFA算法,时间复杂度
O
(
k
m
)
O(km)
O(km);
3.
3.
3. 枚举各个点,比较出
d
m
a
x
[
k
]
−
d
m
i
n
[
k
]
dmax[k]-dmin[k]
dmax[k]−dmin[k] 的最大值。
AC代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
const int N = 1e5 + 10, M = 4e6 + 10, INF = 0x3f3f3f3f;
int n, m;
int hs[N], ht[N], ne[M], e[M], w[N], idx;
int st[N], dmin[N], dmax[N];
void add(int h[], int a, int b)
{
e[idx] = b;
ne[idx] = h[a];
h[a] = idx++;
return;
}
void spfa(int h[], int dis[], int state)
{
queue<int> q;
if (state == 0)
{
memset(dis, INF, sizeof dmin);
dis[1] = w[1];
q.push(1);
}
else
{
memset(dis, -INF, sizeof dmax);
dis[n] = w[n];
q.push(n);
}
while (!q.empty())
{
int now = q.front();
q.pop();
st[now] = false;
for (int i = h[now]; i != -1; i = ne[i])
{
int j = e[i];
if ((state == 0 && min(dis[now], w[j]) < dis[j]) ||
(state == 1 && max(dis[now], w[j]) > dis[j]))
{
if (state == 0) dis[j] = min(dis[now], w[j]);
else dis[j] = max(dis[now], w[j]);
if (!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
return;
}
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]);
for (int i = 1; i <= m; i++)
{
int a, b, z;
scanf("%d%d%d", &a, &b, &z);
add(hs, a, b);
add(ht, b, a);
if (z == 2)
{
add(hs, b, a);
add(ht, a, b);
}
}
spfa(hs, dmin, 0); //正向跑dmin数组
spfa(ht, dmax, 1); //反向跑dmax数组
int res = 0;
for (int i = 1; i <= n; i++)
{
res = max(res, dmax[i] - dmin[i]);
}
printf("%d", res);
return 0;
}


被折叠的 条评论
为什么被折叠?



