图的最短路径及负圈判断

 主要基本与FloydBellman-fordSPFA 和 Dijkstra三种方法进行做题,以例题来说明。

这里给出四种算法大致对比    https://blog.csdn.net/v_JULY_v/article/details/6181485

下面进入到今天的题目

POJ 1860 Currency Exchange        http://poj.org/problem?id=1860

题目描述的概括 :

有N货币,有M个能把a、b两种货币之相互兑换的点,对每个M点,给出6个数据分别a,b货币,a兑换成b的汇率与拥金,b兑换成a的汇率与拥金。问Nike能不能经过一些兑换操作后,使原金额V增加。例如,如果你想换100美元到俄罗斯卢布兑换点,那里的汇率是29.75,而佣金是0.39,你会得到(100 - 0.39)×29.75=2963.3975卢布。

这里我们可以先把题目抽象为图,把N个货币看成N个结点,M为2 * M条边,其中每2条边分别对应了2个结点,a -> b 和 b -> a;权值就是兑换后的货币数即当拥有货币A的数量为dis[A]时,dis[B] = (dis[A] - Cab) * Rab (这里有个关键是,这里是边权不是简单是求加权,而是x不断变化的条件下,dis[B]随着dis[A]变化而再次变化)。到这里我们的图就大致建好了。

回到问题上来,如何判断金额有无增加呢?从题目中我们可以看到一个重要条件,就是最后判断金额是要回到初始货币类型的,也就是说,我们走的是一条回路(回到起点)!这时候我们可以想到最短路径中有判断负圈的说法,那么我们能不能反思向维,既然我们要求的是最大金额,那么是最大路径判断正圈。

最后得出结论 :从s出发的 “最短路径” 中如果出现了 “负圈”(其实是最大路径出现正圈),那么金额就可以在通过一些兑换后增加。(SPFA, Bellman-ford, Floyd均可实现)。

代码段(以SPFA写法为例)

#include<iostream>
#include<vector>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;

#define el '\n'
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define lop(i, a, b) for(int i = (a); i <  (b); i++)
#define dwn(i, a, b) for(int i = (a); i >= (b); i--)
#define ms(a, b) memset(a, b, sizeof(a))

const int MAXN = 200;
bool vis[MAXN];     //记录有无入队列情况
double dis[MAXN];   //到起点的最大距离,初始都为0
int num[MAXN];      //进入队列次数
int n, m, s;        //n个结点,m条边,初始点为s
double v;           //初始货币数
struct edge{
    int from, to;
    double R, C;
}temp;
vector<edge> e[MAXN];

bool spfa(int star){//用spfa求最大路径判断正圈
    queue<int> Q;
    Q.push(star);
    vis[star] = true;
    dis[star] = v;
    num[star]++;
    while(!Q.empty()){
        int u = Q.front();
        Q.pop();
        vis[u] = false;
        lop(i, 0, e[u].size()){
            int next = e[u][i].to;
            if(dis[next] < (dis[u] - e[u][i].C) * e[u][i].R){
                dis[next] = (dis[u] - e[u][i].C) * e[u][i].R;
                if(!vis[next]){
                    Q.push(next);
                    vis[next] = true;
                    num[next]++;
                    if(num[next] > n)//当一个点进队次数超过n次,说明已经无限循环了,有正圈
                        return true;
                }
            }
        }
    }
    return false;
}

int main(){
    cin >> n >> m >> s >> v;
    rep(i, 1, m){//输入
        int a, b;
        double r1, r2, c1, c2;
        cin >> a >> b >> r1 >> c1 >> r2 >> c2;
        temp.to = b, temp.R = r1, temp.C = c1;
        e[a].push_back(temp);
        temp.to = a, temp.R = r2, temp.C = c2;
        e[b].push_back(temp);
    }
    if(spfa(s))
        cout << "YES\n";
    else
        cout << "NO\n";
    return 0;
}

补充一个差分约束的思想(博主对这个理解不是很透彻,所以借用一下大佬们的解释)

https://www.cnblogs.com/zzz-hhh/p/11200893.htmlicon-default.png?t=M276https://www.cnblogs.com/zzz-hhh/p/11200893.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值