题意是:有n种货币(n个节点),2*m种兑换方式(2*m条边),初始值是持有价值为v的某种货币s。问经任意的兑换之后再回到货币s,是否可能使持有价值增加。其中兑换方式是这样的:若货币A和B之间能兑换,则会同时给出Rab,Cab,Rba,Cba,从A到B兑换:VB = (VA-Cab)*Rab,从B到A兑换:VA=(VB-Cba)*Rba。
输入先是一行4个数:n,m,s,v。
然后是m行,每行6个数:A,B,Rab,Cab,Rba,Cba。
想了很久才想明白这个题为什么和bellman-ford完全相同。根据题意,图中有n个点,有2*m条边,每两点之间,要么没有直接的边相连,要么是双向直接相连,这是题目的一点限制,不过不影响算法。这个人手中的货币价值,就是bellman-ford中的节点到原点的距离,就是最终要最大化或最小化的量。只是现在这个“距离”不是简单的把各边权值加起来,而是稍复杂一点的加减乘除运算。bellman-ford的思想是所用边数的budget从0到n-1循环,每个循环里对每个顶点求min(dist[v])。它的“松弛“技术似乎更好理解一些,松弛技术是在每个循环里对每条边进行松弛,看这条边的dst顶点是否要update dist。那么现在几乎在每一点都找到了与bellman-ford对应之处,唯一不同就是给了上个循环的dist,如何求本次dist,这只需要把加法换成题目所给的公式即可。
其实bellman-ford里的负圈,也不仅限于所有边权重加起来是负数的圈。从某点开始,经过每条边自己规定的运算,走一圈又回到这个点,发现要优化的值向我们想要的方向发展了,就说明这是个负圈。比如要求最大值,走一圈发现我们的target变大了,那么总可以不断地走这个圈是其不断变大,这就是负圈了。考察是否有负圈,只需在第n个循环看是否还有update,如果有,说明有负圈。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAXN = 210;
float d[MAXN];
float w[MAXN][2];
int u[MAXN];
int v[MAXN];
int main()
{
int n, m, s;
float y;
scanf("%d%d%d%f", &n, &m, &s, &y);
int len = 0;
for (int t = 0; t < m; ++t)
{
int a,b;
scanf("%d%d%f%f%f%f", &a, &b, &w[len][0], &w[len][1], &w[len+1][0], &w[len+1][1]);
u[len] = a;
v[len] = b;
u[len+1] = b;
v[len+1] = a;
len+=2;
}
for (int k = 0; k < MAXN; ++k) d[k] = 0;
d[s] = y;
for (int k = 0; k <= n; ++k)
{
bool update = false;
for (int i = 0; i < 2*m; ++i)
{
if (d[v[i]] < (d[u[i]]-w[i][1])*w[i][0])
{
update = true;
d[v[i]] = (d[u[i]]-w[i][1])*w[i][0];
if (k == n)
{
printf("YES\n"); return 0;
}
}
}
if (!update) break;
}
printf("NO\n");
return 0;
}