poj3259 - Wormholes (bellman_ford/spfa求负环模板题)

题目链接:http://poj.org/problem?id=3259

题意:一个农场主,有n个农场,农场之间有m条双向路,每条路有花费时间权值,有w个虫洞以路的形式给出,权值为可以回到多久之前。

 

思路:虫洞可以看成是一条负权路,问题就转化成求一个图中是否存在负权回路;

 

1.bellman_ford算法

求距离用模板就行了,怎么判断有没有负权回路?

判断边集E中的每一条边的两个端点是否收敛。

通俗点就是判断每条边两个顶点到源点的距离差是否就是边值。

如果差值都是边值很明显无负权回路,反正则有。

即有负权回路时,存在d[边的终点]>d[边的起点]+边的权值。why?

因为有负权回路时,遍历最后一遍是,环的起点更新变的更小,而与它连边的点没有变。

没懂?看下面例子就知道了。

比例:有三条边:1-->2 权值为1, 2-->3权值为2,  3-->1权值为-4。构成负权回路,bellman_ford算法会遍历n-1次(这里为2次)全部的边

假设起点为1,终点为3,d[]表示到点1的距离。

第一遍: d[1]=0,d[2]=1, d[3]=3,  遍历3-->1时 d[1]=-1;

第一遍: d[1]=-1, d[2]=0, d[3]=2,  遍历3-->1时 d[1]=-2;

可以看出环的起点1的距离变小了,但后面的边没来得及更新变得更小,所以使得d[2]=0>d[1]+1=-1。

 

代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=6000;
struct node{
    int next,from,to,w;
}edge[maxn];

int head[maxn],d[maxn],cnt,n,m,z;
void init()
{
    memset(head,-1,sizeof(head));
    memset(d,0x3f,sizeof(d));
    cnt=0;
}

void add(int u,int v,int w)//前向星连边 
{
    edge[cnt].from=u;
    edge[cnt].to=v;
    edge[cnt].w=w;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}

bool bellmanford()
{
    d[1]=0;
    for(int i=0;i<n;i++)//板子 
    {
        for(int j=0;j<cnt;j++)
            if(d[edge[j].to]>d[edge[j].from]+edge[j].w)
                d[edge[j].to]=d[edge[j].from]+edge[j].w;
    }
    for(int i=0;i<cnt;i++)//判断是否存在负权回路 
        if(d[edge[i].to]>d[edge[i].from]+edge[i].w)
            return true;
    return false;
}

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        init();
        scanf("%d%d%d",&n,&m,&z);
        int a,b,c;
        for(int i=0;i<m;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,c);
            add(b,a,c);
        }
        for(int i=0;i<z;i++)//虫洞权值变负 
        {
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,-c);
        }
        if(bellmanford())    printf("YES\n");
        else    printf("NO\n");
    }
    return 0;
}
View Code

 

2.spfa算法

那spfa怎么求负权回路呢?这个就比上面简单了。

我们知道spfa如果碰到负权回路就会一直死循环,因为他会一直更新最短的路,有负权回路,环里的路权值会越来越小。

如果没有负权回路,两点间有最短路的话,那么每个结点最多经过一次。也就是说,这条路不超过n-1条边。

所以只要判断每个点入队的次数,次数>n-1就有负权回路了。

 

代码:

 

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#include<map>
#include<vector>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=6000;
struct node{
    int to,next,w;
}edge[maxn];
int head[maxn],d[maxn],vis[maxn],num[maxn],cnt,n,m,z;

void add(int u,int v,int w)
{
    edge[cnt].to=v;
    edge[cnt].w=w;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}

bool spfa()//spf板子 
{
    memset(vis,0,sizeof(vis));
    memset(num,0,sizeof(num));
    queue<int> q;
    vis[1]=1;
    d[1]=0;
    num[1]++;
    q.push(1);
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        vis[u]=0;
        for(int i=head[u];i!=-1;i=edge[i].next)
        {
            int v=edge[i].to;
            if(d[v]>d[u]+edge[i].w)
            {
                d[v]=d[u]+edge[i].w;
                if(!vis[v])
                {
                    vis[v]=1;
                    q.push(v);
                    num[v]++;
                    if(num[v]>=n)//判断入队次数,是否构成负权回路。 
                        return true;
                }
            }
        }
    }
    return false;
}

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%d",&n,&m,&z);
        cnt=0;
        memset(head,-1,sizeof(head));
        for(int i=1;i<=n;i++)
            d[i]=inf;
        int a,b,c;
        for(int i=0;i<m;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,c);
            add(b,a,c);
        }
        for(int i=0;i<z;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,-c);//虫洞权值变负 
        }
        if(spfa())    printf("YES\n");
        else    printf("NO\n");
    }
    return 0;
}
View Code

 

posted @ 2018-12-04 17:54  怀揣少年梦.#  阅读(143)  评论(0编辑  收藏  举报