题意:
N种货币,M个兑换点,S开始的货币类型,V开始拥有的货币数
给出M个兑换点的信息
a b 代表兑换的两种货币,然后给出a兑换b的汇率和佣金,b兑换a的汇率和佣金
问是否可以经过多轮兑换后使得本金变多
思路:
判断是否有正环,那么循环无数次之后一定是可以赚回本钱的,至于有些人循环一圈之后就与本金比是否增大是不可取的,因为万一转为本金的佣金很高,但是存在一个一次增加一点点的正环的话就需要循环很多次才可以高于本金。
Bellman-Ford算法判正环
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
using namespace std;
#define ms(x, n) memset(x,n,sizeof(x));
typedef long long LL;
const int inf = 1<<30;
const LL maxn = 110;
int N, M, S;
double V;
struct Edge{
double r, c;
int u, v;
Edge(int uu, int vv, double rr, double cc){u = uu, v = vv, r = rr, c = cc;}
};
vector<Edge> es;
double d[maxn]; //到达该点可获得的最大金额
bool BellmanFord(){
ms(d, 0);
d[S] = V;
for(int i = 1; i < N; ++i){
bool flag = false;
for(int j = 0; j < es.size(); ++j){
if((d[es[j].u]-es[j].c)*es[j].r > d[es[j].v]){
d[es[j].v] = (d[es[j].u]-es[j].c)*es[j].r ;
flag = true;
}
}
if(!flag) break;
}
for(int j = 0; j < es.size(); ++j)
if((d[es[j].u]-es[j].c)*es[j].r > d[es[j].v])
return true;
return false;
}
int main()
{
int a, b;
double Rab, Rba, Cab, Cba;
cin >> N >> M >> S >> V;
while(M--){
cin >> a >> b >> Rab >> Cab >> Rba >> Cba;
es.push_back(Edge(a, b, Rab, Cab));
es.push_back(Edge(b, a, Rba, Cba));
}
if(BellmanFord()) cout << "YES" << endl;
else cout << "NO" << endl;
return 0;
}
/*
* Bellman-Ford算法板子
*/
#include <stdio.h>
#include <string.h>
#define N 505
#define inf 100000
int dis[N]; //最小距离
int n, m;
struct Cow
{
int x;//起点
int y;//终点
int w;//权值
}cow[5300];
int Bellman_ford(int m)
{
int i, j, k, t;
int ok;
for(i=1; i<=n; i++)
dis[i] = inf;
dis[1] = 0;
//对每一个点进行松弛 尽可能的让路径最小
for(i=1; i<=n-1; i++)
{
ok = 1;
for(j=1; j<=m; j++)
{
if(dis[cow[j].x] > dis[cow[j].y] + cow[j].w)
{
dis[cow[j].x] = dis[cow[j].y] + cow[j].w;
ok = 0;
}
}
//如果没有点进行更新 那么跳出即可 可减小时间复杂度
if(ok)
break;
}
//判断图中是否有负权值存在
for(j=1; j<=m; j++)
{
if(d[cow[j].x] > d[cow[j].y] + cow[j].w)
return 0;
}
return 1;
}
int main()
{
int i, j, t, x;
int a, b, c, w;
scanf("%d", &t);
while(t--)
{
scanf("%d%d%d", &n, &m, &w);
memset(edges, 0, sizeof(edges));
//因为是无向图 所以需要将所有的添加进去
for(i=0, j=1; j<=m;j++)
{
scanf("%d%d%d", &a, &b, &c);
i++;
cow[i].x = a;
cow[i].y = b;
cow[i].w = c;
i++;
cow[i].x = b;
cow[i].y = a;
cow[i].w = c;
}
for(j=1; j<=w; j++)
{
scanf("%d%d%d", &a, &b, &c);
i++;
cow[i].x = a ;
cow[i].y = b;
cow[i].w = -c;
}
if(Bellman_ford(i))
printf("NO\n");
else
printf("YES\n");
}
return 0;
}
Bellman-Ford O(NE)算法思想:
这种算法的思路非常简单,运用了Dijkstra的蓝白点思想,一开始认为起点为白点,其他的都是蓝点,因为起点一定会与一些点相连,所以我们每一次枚举所有的边,其中的一些边就会连接白点和蓝点,然后用所有的白点来修改蓝点,来得到最短路径。
但是,Ford算法也有缺点,当有负权回路时,求出的最短路径将会报错,因为有负权回路的时候,我们会绕它走无数圈来得到最小的答案。
Bellman-Ford的改进方法是SPFA,就是用队列来减少不必要的计算
SPFA的大致思想:
算法大致流程是用一个队列来进行维护。 初始时将源加入队列。 每次从队列中取出一个元素,并对所有与他相邻的点进行松弛,若某个相邻的点松弛成功,如果该点没有在队列中,则将其入队。 直到队列为空时算法结束。
SPFA的判负环的原理:
我们都知道spfa算法是对bellman算法的优化,那么如何用spfa算法来判断负权回路呢?我们考虑一个节点入队的条件是什么,只有那些在前一遍松弛中改变了距离估计值的点,才可能引起他们的邻接点的距离估计值的改变。因此,用一个先进先出的队列来存放被成功松弛的顶点。同样,我们有这样的定理:“两点间如果有最短路,那么每个结点最多经过一次。也就是说,这条路不超过n-1条边。”(如果一个结点经过了两次,那么我们走了一个圈。如果这个圈的权为正,显然不划算;如果是负圈,那么最短路不存在;如果是零圈,去掉不影响最优值)。也就是说,每个点最多入队n-1次(这里比较难理解,需要仔细体会,n-1只是一种最坏情况,实际中,这样会很大程度上影响程序的效率)。
SPFA算法 判正环
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
int n,m,s;
double v;
struct Node
{
int y;
double val;
double sub;
int next;
Node(int y=0,double val=0,double sub = 0,int next=0):y(y),val(val),sub(sub),next(next) {}
} node[205];
int cnt,head[105];
void add(int x,int y,double val,double sub)
{
node[++cnt].y=y;
node[cnt].next=head[x];
node[cnt].val=val;
node[cnt].sub=sub;
head[x]=cnt;
}
queue<int>que;
double dist[105];
int vis[105];
int tot[105];
int num[105];
bool spfa()
{
while(!que.empty())que.pop();
memset(vis,0,sizeof(vis));
memset(dist,0,sizeof(dist));
memset(num,0,sizeof(num));
que.push(s);
dist[s] = v;
while(!que.empty())
{
int from = que.front();
que.pop();
vis[from] = 0;
for(int i=head[from]; i; i=node[i].next)
{
int to = node[i].y;
if(dist[to] < (dist[from]-node[i].sub)*node[i].val)
{
dist[to] = (dist[from]-node[i].sub)*node[i].val;
if(!vis[to])
{
que.push(to);
vis[to]=1;
num[to]++;
if(num[to]>= n)return 1;
}
}
}
// if(dist[s] > v)return 1;
}
return 0;
}
int main()
{
scanf("%d%d%d%lf",&n,&m,&s,&v);
for(int i=1; i<=m; i++)
{
int u,v;
double a,b,c,d;
scanf("%d%d",&u,&v);
scanf("%lf%lf%lf%lf",&a,&b,&c,&d);
add(u,v,a,b);
add(v,u,c,d);
}
int ans = spfa();
if(ans)printf("YES\n");
else printf("NO\n");
}