负权环的判断方法
根据松弛次数
n代表节点个数。
根据松弛次数是否大于等于n来判断负权环,是从网上其他博客说的,根据出队次数是否大于等于n来判断,想到的。因为,判断出队次数,是判断更新次数的上界。
用一个n大小的数组来代表每个点的松弛次数。因为SPFA算法里的松弛,和Bellman-ford算法里的松弛一样。Bellman-ford算法里,对同一个点的松弛次数,在极端情况下,可以想象把这些松弛次数分配到每一次迭代求解中去,而迭代求解一共只有n-1次。所以一旦某个点的松弛次数等于了n,那么就说明有负环。
所以,在每次进行松弛后,遍历判断每个点的松弛次数,如果有一个等于n(再执行下去就会大于n),就说明有负环。
题目解析
题目链接,这道题还是比较有意思的,以每种货币为节点,一个兑换所提供两条边,我们需要找到这个图中是否有一个环使得我们的money可以无限增多,从负环到这里的变化,无非是改变了松弛条件,建好图,利用上述的性质便可得到。
#define ll long long
#define vec vector<int>
#define P pair<int,int>
#define MAX 105
int N, M, S, A, B, vis[MAX], num[MAX];
double V, c, a1, a2, a3, a4, mon[MAX];
struct edge {
int to; double rate, cost;
edge(int a = 0, double b = 0, double c = 0) { to = a, rate = b, cost = c; }
};
vector<edge> G[MAX];
void addEdge(int a,int b, double a1,double a2, double a3, double a4) {
G[a].push_back(edge(b, a1, a2));
G[b].push_back(edge(a, a3, a4));
}
void spfa() {
memset(mon, 0, sizeof(mon));
memset(vis, 0, sizeof(vis));
memset(num, 0, sizeof(num));
queue<int> q; q.push(S);
mon[S] = V; vis[S] = 1;
int cnt = -1;
while (!q.empty()) {
int u = q.front(); vis[u] = 0; q.pop(); num[u]++;
if (num[u] > N) { cnt = u; break; };//存在一条可以不断增加money的路
//注意这里是先num++的,因此大于N+1时才结束
for (int i = 0; i < G[u].size(); i++) {
edge e = G[u][i];
if ((mon[u] - e.cost)*e.rate > mon[e.to]) {
mon[e.to] = (mon[u] - e.cost)*e.rate;
if (!vis[e.to]) {
q.push(e.to);
vis[e.to] = 1;
}
}
}
}
if (cnt == -1)cout << "NO" << endl;
else cout << "YES" << endl;
}
int main() {
while (cin >> N >> M >> S >> V) {
//以钱币的种类为点
for (int i = 1; i <= N; i++)G[i].clear();
for (int i = 1; i <= M; i++) {
cin >> A >> B >> a1 >> a2 >> a3 >> a4;
addEdge(A, B, a1, a2, a3, a4);
}
spfa();
}
}