Wormholes(虫洞) 判断负环 + 队列优化Bellman邻接表实现

Wormholes 图中有无负环的判断

John在他的农场中闲逛时发现了许多虫洞。虫洞可以看作一条十分奇特的有向边,并可以使你返回到过去的一个时刻(相对你进入虫洞之前)。John的每个农场有M条小路(无向边)连接着N (从1…N标号)块地,并有W个虫洞(有向边)。其中1<=N<=500,1<=M<=2500,1<=W<=200。 现在John想借助这些虫洞来回到过去(出发时刻之前),请你告诉他能办到吗。 John将向你提供F(1<=F<=5)个农场的地图。没有小路会耗费你超过10000秒的时间,当然也没有虫洞回帮你回到超过10000秒以前。

【输入格式】

  • Line 1: 一个整数 F, 表示农场个数。

  • Line 1 of each farm: 三个整数 N, M, W。

  • Lines 2…M+1 of each farm: 三个数(S, E, T)。表示在标号为S的地与标号为E的地中间有一条用时T秒的小路。

  • Lines M+2…M+W+1 of each farm: 三个数(S, E, T)。表示在标号为S的地与标号为E的地中间有一条可以使John到达T秒前的虫洞。

【输出格式】

  • Lines 1…F: 如果John能在这个农场实现他的目标,输出"YES",否则输出"NO"。

//翻译转载

一个图上有正权边和负权边,判断是否可以构成负权回路。
判断负权回路,那我们要用到Bellman算法了,之前没怎么用过邻接表存图,在这里放一个写的模板。

/*
核心:
    原始:枚举每条边,用边收缩到源点的距离,进行n-1次枚举收缩操作(无负环则最多n-1次即可)
    队列优化:用边去收缩每个点到源点的距离,边选择到源点的距离改变了的点的临边
一开始把源点放入队列,从队列中取点,用这些点的临边尝试收缩,再把成功收缩了的点放入队列
如果点已经在队列中,就跳过

Bellman和Dijkstra的不同:
B的book数组是用来标记点是否在队列中,若点弹出,book就要复0,D的book数组是用了标记是否用这个点为中转收缩过,**每个点只作为中转1次**。
B的队列是普通队列,D的为以到源点距离为判断标准的优先队列

Bellman时间复杂度为n*m,即使优化后,时间复杂度还是玄学,除了处理负权负环外,应使用Dijkstra。(若n*m不会超时间当然可以)
*/
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;

#define Max 0x3f3f3f3f

int node[505],book[505],num[505],dis[505],cnt/*边容量*/;

struct E{
    int b,c;
    int next;
}e[6000];

void add(int a,int b,int c){
    cnt++;
    e[cnt].b = b;
    e[cnt].c = c;
    e[cnt].next = node[a];
    node[a] = cnt;
}
void init(int n,int m){
    cnt = 0;
    memset(node,-1,sizeof(node));
    memset(book,0,sizeof(book));
    memset(num,0,sizeof(num));
    memset(dis,Max,sizeof(dis));
    /*
    for(int i=1; i<=n; i++){
        node[i] = -1;
        book[i] = 0;
        num[i] = 0;
        dis[i] = Max;
    }
    */
    for(int i=1; i<=m; i++) e[i].next=-1;
}

void Bell(int n){ //n个点
    queue<int>que;
    int f=0; //默认无负环
    que.push(1);
    dis[1] = 0;     //判断负环,随便从1号点搜就可以了
    book[1] = 1;
    while(!que.empty()){
        int x=que.front(); que.pop(); //取走就弹出 大佬告诉我的好习惯
        book[x] = 0;
        int k=node[x];
        while(k!=-1){
            int b=e[k].b, c=e[k].c;//x的邻点
            if(dis[b]>dis[x]+c){
                dis[b] = dis[x]+c;
                num[b]++;
                if(num[b]>=n){
                    f = 1;
                    break;
                }
                if(book[b]==0) que.push(b);
            }
            k = e[k].next;
        }
        if(f==1) break;//有环,无限收缩,退出
    }
    if(f==1) printf("YES\n");
    else     printf("NO\n");
}

int main()
{
    int F, n,m,w;
    scanf("%d",&F);
    for(int I=1; I<=F; I++){
        scanf("%d%d%d",&n,&m,&w);
        init(n , m*2+w);
        for(int i=1; i<=m; i++){
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,c);
            add(b,a,c);
        }
        for(int i=1; i<=w; i++){
            int a,b,c;
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,-c);
        }
        Bell(n);
    }
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值