Coins Respawn(负环,最短路)
题意
给定\(N\)个点\(M\)条边的有向图,对于每条边\((u_i, v_i)\),其边权为\(c_i\)。
假设\(1\)号点到\(N\)号点的一条路径长度为\(d\),求\(d - T \times P\)的最大值。其中\(T\)为路径的边数,\(P\)为给定常数。
如果不存在最大值(陷入环中),则输出\(-1\)。
题目链接:https://atcoder.jp/contests/abc137/tasks/abc137_e
数据范围
\(2 \leq N \leq 2500\)
\(1 \leq M \leq 5000\)
思路
对于这种与边数或者点数有关的问题,一个常规的套路是,将每条边的边权或点权减去\(P\)。
对于每条边,边权减去\(P\),问题就转化成了从\(1\)到\(N\)的最长路。将边权取反,即:\(P - c_i\),问题可以转化为从\(1\)到\(N\)的最短路问题。
对于不存在最大值的情况,一定是陷入了负环当中。但是这不代表存在负环就一定不存在最大值。因为如果一个点在负环上,但是它并不能通往点\(N\),那么它不会出现在从\(1\)到\(N\)的路径上。
那么我们需要判断的其实是能够通往点\(N\)的所有点中,是否存在位于一个负环上的。
因此,算法流程就是:先建立反图,然后通过BFS/DFS找到所有能够通往点\(N\)的点;由于可能存在负环,因此使用SPFA算法求出是否存在负环,如果不存在则求出最短路。
注意,使用SPFA算法的时候,从一个点向周围点进行拓展,只需要拓展能够通往点\(N\)的点。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 2510, M = 4 * N;
int n, m, p;
int h[N], hs[N], e[M], w[M], ne[M], idx;
int cnt[N], d[N];
bool st[N], connect[N];
void add(int h[], int a, int b, int c)
{
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx ++;
}
void bfs()
{
queue<int> que;
que.push(n);
connect[n] = true;
while(que.size()) {
int t = que.front();
que.pop();
for(int i = hs[t]; ~i; i = ne[i]) {
int j = e[i];
if(!connect[j]) {
que.push(j);
connect[j] = true;
}
}
}
}
int spfa()
{
queue<int> que;
memset(d, 0x3f, sizeof d);
d[1] = 0;
st[1] = true;
que.push(1);
while(que.size()) {
int t = que.front();
que.pop();
st[t] = false;
for(int i = h[t]; ~i; i = ne[i]) {
int j = e[i];
if(connect[j] && d[j] > d[t] + w[i]) {
d[j] = d[t] + w[i];
if(!st[j]) {
st[j] = true;
que.push(j);
if(++ cnt[j] > n) return -1;
}
}
}
}
return max(0, -d[n]);
}
int main()
{
scanf("%d%d%d", &n, &m, &p);
memset(h, -1, sizeof h);
memset(hs, -1, sizeof hs);
for(int i = 0; i < m; i ++) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(h, a, b, p - c), add(hs, b, a, 1);
}
bfs();
printf("%d\n", spfa());
return 0;
}