POJ1860_通过SPFA寻找无限正循环

/*
适用范围:给定的图存在负权边,这时类似Dijkstra等算法便没有了用武之地,而Bellman-Ford算法的复杂度又过高,SPFA算法便派上用场了。 我们约定有向加权图G不存在负权回路,即最短路径一定存在。当然,我们可以在执行该算法前做一次拓扑排序,以判断是否存在负权回路,但这不是我们讨论的重点。
算法思想:我们用数组d记录每个结点的最短路径估计值,用邻接表来存储图G。我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止


期望的时间复杂度O(ke), 其中k为所有顶点进队的平均次数,可以证明k一般小于等于2。


实现方法:

  建立一个队列,初始时队列里只有起始点,再建立一个表格记录起始点到所有点的最短路径(该表格的初始值要赋为极大值,该点到他本身的路径赋为0)。然后执行松弛操作,用队列里有的点作为起始点去刷新到所有点的最短路,如果刷新成功且被刷新点不在队列中则把该点加入到队列最后。重复执行直到队列为空。

  判断有无负环:
    如果某个点进入队列的次数超过N次则存在负环(SPFA无法处理带负环的图)
*/


#include<iostream>
#include<vector>
#include<queue>
#include<cstdio>
#include<cstring>
#define Max 0x3f3f3f3f
using namespace std;

const int maxn = 107;

//货币种类是点,兑换商店是边.亮点之间有多条边
//一共有200条边,100个点

typedef struct node
{
    double c;
    double r;
}node;

vector<node> map[maxn][maxn];//map[i][j].size()==0表示两点之间并没有边
bool vis[maxn];
int cnt[maxn];
double D[maxn];
vector<int> P[maxn];

bool spfa(int id,int n,double v)//只要出现收入增加的环,而且可以转化为id种货币(只要是从id中货币出去的就行)
{
    memset(vis, 0, sizeof(vis));
    memset(cnt, 0, sizeof(cnt));
    queue<int> Q;

    for (int i = 0; i < n; i++)
    {
        D[i] = 0;
    }

    Q.push(id);
    vis[id] = 1;//vis数组标记队列里面的元素
    cnt[id]++;//
    D[id] = v;

    while (!Q.empty())
    {
        //取出队列头部元素
        int u = Q.front();
        Q.pop();
        vis[u] = 0;
        for (int i = 0; i < P[u].size(); i++)
        {
            int v = P[u][i];//终点
            double t = 0;//现有货币
            for (int j = 0; j < map[u][v].size(); j++)
            {
                double tt = D[u] - map[u][v][j].c;
                if ( tt >= 0)
                {
                    t = max(t, tt*map[u][v][j].r);
                }
            }
            if (t > D[v])//实现增长
            {
                D[v] = t;
                if (!vis[v])//之前并没有在队列中,将其入队
                {
                    Q.push(v);
                    vis[v] = 1;
                    cnt[v]++;
                    if (cnt[v] > n)//入队超过n次,说明形成无限增长
                    {
                        return true;
                    }
                }
            }
        }
    }
    return false;
}



int main()
{
    int n, m, s;
    double v;
    cin >> n >> m >> s >> v;
    int a, b;
    s--;
    node an, bn;
    for (int i = 0; i < m; i++)
    {
        cin >> a >> b >> an.r >> an.c >> bn.r >> bn.c;
        a--;
        b--;
        map[a][b].push_back(an);
        map[b][a].push_back(bn);
    }
    for (int i = 0; i < n; i++)//通过创建P[i]表示从i出去可以到达的边,来实现降低复杂度
    {
        for (int j = 0; j <= i; j++)
        {
            if (map[i][j].size() > 0)
            {
                P[i].push_back(j);
                if (i != j)
                {
                    P[j].push_back(i);
                }
            }
        }
    }

    if (spfa(s, n, v))
    {
        cout << "YES" << endl;
    }
    else
    {
        cout << "NO" << endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值