讲题之前,先纪念一下我的 vj 700题( ̄▽ ̄)~*,接下来向 800 题冲冲冲····
知识点:传送门
题意
- 给我们一个带有边权的有向图,边权可以为 正 或 负。
- 让我们求图中的图中的最短路,
- 注意有向图可能形成 负环,如果有负环输出 -inf。
思路
- 我们首先 虚拟出来一个超级源点 0, 让后让 0 点与 1~n 中的每个点都练接一条长 0 的有向边,
- 然后从 0 节点开始跑最短,因为存在负环所以不能用 dijkstar,可以用 spfa 来跑最短路,
- 如果图中存在负环,负环上的点会一会被放到队列 q 中,更新越来越小,这时候我们只需要统计某个点是否 进入队列超过 n 次(一点最不会被入队更新超过 n 次),如果是的话就有环,否则输出从超级源点 0 到其点的最小距离就行,
- 分析超级源点的作用,我们不能对每个点跑最短路 spfa 并判断负环,建立超级源点之后只需要跑一次就行了,
最后考虑所有边都是正权值的情况,这样建立超级源点跑完之后的答案为 0
,是不符合题意的, 这个时候我们提前判断输出就行了。
bfs 版 spfa代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#include <string>
#include <queue>
#include <map>
#include <bitset>
#include <vector>
using namespace std;
void fre() { system("clear"), freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { system("clear"), freopen("A.txt", "r", stdin);}
void Run(int x = 0) {
#ifdef ACM //宏定义免注释 freopen
if (! x) fre(); else Fre();
#endif
}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define db double
#define ll long long
#define ull unsigned long long
#define Pir pair<ll, ll>
#define m_p make_pair
#define for_(i, s, e) for(ll i = (ll)(s); i <= (ll)(e); i ++)
#define rep_(i, e, s) for(ll i = (ll)(e); i >= (ll)(s); i --)
#define memset(a, b, c) memset(a, (int)b, c);
#define size() size() * 1LL
#define sc scanf
#define pr printf
#define sd(a) sc("%lld", &a)
#define ss(a) sc("%s", a)
#define __ pr( "------------------------\n" );
#define ___ pr("\n------------------------\n");
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define esp 1e-7
#define mod (ll)(1e9 + 7)
/*=========================ACMer===========================*/
const int mxn = 5005;
int n, m;
struct Edge
{
int v, w, nxt;
} edge[mxn << 1];
int head[mxn], tot;
int vis[mxn];
ll dis[mxn];
int tim[mxn]; //某个点入队/出队的次数
void init(int n)
{
memset(head, 0, sizeof head);
tot = 0;
}
void Add(int u, int v, int w)
{
edge[++ tot] = (Edge){ v, w, head[u] };
head[u] = tot;
}
bool spfa(int u)
{
memset(vis, 0, sizeof vis);
memset(dis, inf, sizeof dis);
memset(tim, 0, sizeof tim);
vis[u] = 1;
dis[u] = 0;
tim[u] = 1;
queue<int> q;
q.push(u);
while (q.size())
{
u = q.front(); q.pop();
vis[u] = 0; //u 出队列了,解除标记
for (int i = head[u]; i; i = edge[i].nxt)
{
int v = edge[i].v;
int w = edge[i].w;
if (dis[v] > dis[u] + w)
{
dis[v] = dis[u] + 1LL * w;
if (! vis[v]) //v 不在队列里面
{
vis[v] = 1;
q.push(v);
tim[v] ++;
if (tim[v] > n) return 0;
}
}
}
}
return 1;
}
int main()
{
Run();
int T; sc("%d", &T);
while (T --)
{
sc("%d %d", &n, &m);
init(n);
int u, v, w; ll ans = INF;
for_(i, 1, m)
{
sc("%d %d %d", &u, &v, &w);
Add(u, v, w);
ans = min(ans, w * 1LL);
}
if (ans >= 0)
{
pr("%lld\n", ans);
continue;
}
for_(i, 1, n) //构造超级源点, 这样就不用对每个点都跑一次 spfa 了, 当时要特判断全是 正权边的情况
{
Add(0, i, 0);
}
if (! spfa(0))
{
pr("-inf\n");
continue;
}
for_(i, 1, n)
{
ans = min(ans, dis[i]);
}
pr("%lld\n", ans);
}
return 0;
}
dfs 版 spfa
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#include <string>
#include <queue>
#include <map>
#include <bitset>
#include <vector>
using namespace std;
void fre() { system("clear"), freopen("A.txt", "r", stdin); freopen("Ans.txt","w",stdout); }
void Fre() { system("clear"), freopen("A.txt", "r", stdin);}
void Run(int x = 0) {
#ifdef ACM //宏定义免注释 freopen
if (! x) fre(); else Fre();
#endif
}
#define ios ios::sync_with_stdio(false)
#define Pi acos(-1)
#define pb push_back
#define fi first
#define se second
#define db double
#define ll long long
#define ull unsigned long long
#define Pir pair<ll, ll>
#define m_p make_pair
#define for_(i, s, e) for(ll i = (ll)(s); i <= (ll)(e); i ++)
#define rep_(i, e, s) for(ll i = (ll)(e); i >= (ll)(s); i --)
#define memset(a, b, c) memset(a, (int)b, c);
#define size() size() * 1LL
#define sc scanf
#define pr printf
#define sd(a) sc("%lld", &a)
#define ss(a) sc("%s", a)
#define __ pr( "------------------------\n" );
#define ___ pr("\n------------------------\n");
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define esp 1e-7
#define mod (ll)(1e9 + 7)
/*=========================ACMer===========================*/
const int mxn = 5005;
struct Edge
{
int v, w, nxt;
} edge[mxn << 1];
int head[mxn], tot;
bool circle;
int vis[mxn];
int dis[mxn];
void init(int n)
{
memset(head, 0, sizeof head);
for_(i, 0, n) dis[i] = inf, vis[i] = 0;
tot = 0;
circle = 0;
}
void Add(int u, int v, int w)
{
edge[++ tot] = (Edge){ v, w, head[u] };
head[u] = tot;
}
void spfa(int u)
{
if (circle) return;
vis[u] = 1;
for (int i = head[u]; i; i = edge[i].nxt)
{
int v = edge[i].v;
int w = edge[i].w;
if (dis[v] > dis[u] + w) //如果可以松弛
{
dis[v] = dis[u] + w;
if (! circle && vis[v]) //如果可以松弛,而且松弛的 v 节点是已经访问过的节点,说明存在负环因为之后存在 负环 才能松弛当前路径已访问过的节点
{
circle = 1;
return;
}
else
{
spfa(v);
}
}
}
vis[u] = 0; //回溯解除标记
}
int main()
{
Run();
int T; sc("%d", &T);
while (T --)
{
int n, m;
sc("%d %d", &n, &m);
init(n);
int u, v, w, ans = inf;
for_(i, 1, m)
{
sc("%d %d %d", &u, &v, &w);
Add(u, v, w);
ans = min(ans, w);
}
if (ans >= 0)
{
pr("%d\n", ans);
continue;
}
for_(i, 1, n) //构造超级源点
{
Add(0, i, 0);
}
dis[0] = 0;
spfa(0);
if (circle)
{
pr("-inf\n");
continue;
}
for_(i, 1, n)
{
ans = min(ans, dis[i]);
}
pr("%d\n", ans);
}
return 0;
}