poj3259 Wormholes BellmanFord或SPFA

10 篇文章 0 订阅
4 篇文章 0 订阅

今天学了一下BellmanFord算法(它也是一个求单元最短路的算法),它和不使用优先队列的Dijkstra算法复杂度差不多,但是它可以处理带有负边权的图。

算法实现很简单:给定图G=(V, E), |V| = N。 (1)我们对所有边进行N-1次松弛操作可以得到源点到所有点的最短距离。(2)再对所有边进行一次松弛操作,判断是否存在负环,如果存在负环,则从S到所有点的最短距离不存在,否则求解完毕。


这道题题意:给定一些双向边(普通路径),边权为正,再给定一些单向边,边权为负,问是在这个图里否存在一个负环,存在负环就输出YES,否则输出NO。

直接用这个算法就A了。


#include<stdio.h>
#include<string.h>

const int N = 512;
const int M = 6000; //邻接表存双向边要开边数的双倍
const int INF = 0x3f3f3f3f;

struct Edge {
    int v, w;
    int next;
};

int dis[N];
int fir[N];
Edge edge[M];
int cnt, n;

void init() {
    cnt = 1;
    memset(fir, -1, sizeof(fir));
}

void addEdge(int u, int v, int w) {
    edge[cnt].v = v;
    edge[cnt].w = w;
    edge[cnt].next = fir[u];
    fir[u] = cnt++;
}


bool bellmanFord(int s) {

    memset(dis, INF, sizeof(dis));
    dis[s] = 0;
    for(int i = 1; i < n; i++) {
        for(int j = 1; j <= n; j++) {
            for(int k = fir[j]; k != -1; k = edge[k].next) {
                int v = edge[k].v;
                if(dis[v] > dis[j] + edge[k].w)
                    dis[v] = dis[j] + edge[k].w;
            }
        }
    }
    for(int j = 1; j <= n; j++) {
//        printf("[%d]\n", dis[j]);
        for(int k = fir[j]; k != -1; k = edge[k].next) {
            int v = edge[k].v;
            if(dis[v] > dis[j] + edge[k].w)
                return true;
        }
    }
    return false;
}

int main() {

    int f;
    scanf("%d", &f);
    while(f--) {
        init();
        int m, w;
        scanf("%d%d%d", &n, &m, &w);
        for(int i = 0; i < m; i++) {
            int s, e, t;
            scanf("%d%d%d", &s, &e, &t);
            addEdge(s, e, t);
            addEdge(e, s, t);
        }
        for(int i = 0; i < w; i++) {
            int s, e, t;
            scanf("%d%d%d", &s, &e, &t);
            addEdge(s, e, -t);
        }
        if(bellmanFord(1))
            printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}


Bellman-Ford能解决的通常SPFA都能解决(SPFA是Bellman-Ford的另一种实现方法,故SPFA也是求单元最短路),而且SPFA效率更高。

SPFA的主要步骤:

需要用的存储结构:

邻接表。

队列que<int>。

dis[ u ]:从源点S到u的最短距离。

vis[ u ]:u是否被访问过。

outque[ u ]: u出队的次数。

(1)源点S入队。

(2)访问队首元素,并让其出队,该点出队次数+1,判断该点出队次数是否大于N, 若果大于N则图中存在负环路,否则访问该点的所有连接边,进行松弛操作,如果可以松弛,则更新dis并且把当前顶点入队。

(3)重复(2)直到队列为空。


#include<stdio.h>
#include<string.h>
#include<queue>

using namespace std;

const int N = 512;
const int M = 6000;
const int INF = 0x3f3f3f3f;

struct Edge {
    int v, w, next;
};

Edge edge[M];
int fir[N];
int outque[N];
int vis[N], dis[N];
int cnt, n;

void init() {
    cnt = 1;
    memset(fir, -1, sizeof(fir));
}

void addEdge(int u, int v, int w) {
    edge[cnt].v = v;
    edge[cnt].w = w;
    edge[cnt].next = fir[u];
    fir[u] = cnt++;
}

bool SPFA(int s) {
    memset(dis, INF, sizeof(dis));
    memset(vis, 0, sizeof(vis));
    memset(outque, 0, sizeof(outque));
    queue<int> que;
    dis[s] = 0;
    que.push(s);
    while(!que.empty()) {
        int now = que.front();
        outque[now] ++;
        if(outque[now] > n) return false;
        vis[now] = 0;
        que.pop();
        for(int i = fir[now]; i != -1; i = edge[i].next) {
            int v = edge[i].v;
            if(dis[v] > dis[now] + edge[i].w) {
                dis[v] = dis[now] + edge[i].w;
                vis[v] = 1;
                que.push(v);
            }
        }
    }
    return true;
}

int main() {

    int f;
    scanf("%d", &f);
    while(f--) {
        int w, m;
        init();
        scanf("%d%d%d", &n, &m, &w);
        for(int i = 0; i < m; i++) {
            int u, v, val;
            scanf("%d%d%d", &u, &v, &val);
            addEdge(u, v, val);
            addEdge(v, u, val);
        }
        for(int i = 0; i < w; i++) {
            int u, v, val;
            scanf("%d%d%d", &u, &v, &val);
            addEdge(u, v, -val);
        }
        if(!SPFA(1))
            printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值