先给出题面:https://vjudge.net/problem/UVA-558

题意描述:给你含n个点以及m条边的图,让你判断在这个图中是否存在负权回路。

首先,我们来介绍什么是SPEA算法

  SPFA算法是求解单源最短路径问题的一种算法,由理查德·贝尔曼(Richard Bellman) 和 莱斯特·福特 创立的。有时候这种算法也被称为 Moore-Bellman-Ford 算法,因为 Edward F. Moore 也为这个算法的发展做出了贡献。它的原理是对图进行V-1次松弛操作,得到所有可能的最短路径。其优于迪科斯彻算法的方面是边的权值可以为负数、实现简单,缺点是时间复杂度过高,高达 O(VE)。但算法可以进行若干种优化,提高了效率。

这个算法的思路是:

用数组dis记录每个结点的最短路径估计值,用邻接表或邻接矩阵来存储图G。我们采取的方法是动态逼近法:设立一个先进先出的队列用来保存待优化的结点,优化时每次取出队首结点u,并且用u点当前的最短路径估计值对离开u点所指向的结点v进行松弛操作,如果v点的最短路径估计值有所调整,且v点不在当前的队列中,就将v点放入队尾。这样不断从队列中取出结点来进行松弛操作,直至队列空为止。

具体来说,其核心代码实现思路是:

for(int k = ;k<=n-;k++)
for(int i = ;i<=m;i++)
if(dis[v[i] > dis[u[i] + w[i])
dis[v[i]] = dis[u[i]] + w[i];

看到这段代码是不是有些熟悉,觉得有点像Bellmen-Ford算法,事实上,SPEA就是Bellmen-Ford算法的队列优化。

因此,要想理解SPEA算法,就应该先了解Bellmen-Ford算法,有时间我也会补上一篇关于Bellmen-Ford算法的博客的。

在这里,我就简要的说一下Bellmen-Ford算法的思路

用一个dis数组记录图中各个点到源点(下面将以0为源点讲解)的距离,将dis[0] 初始化为0,其他各点初始化为INF(即无穷大,一般根据题目条件来定),接下来进行n-1轮松弛操作,参考上面的代码,就是判断对于某个点v[i],能否通过源点 0 到 点u[i] ,再从点u[i] 到 点v[i] 这条路径来缩小 源点 0 到 v[i] 的距离。进行完第一轮循环之后,我们得到dis数组事实上就是从0号点出发,只经过一条边,到各个点的最短路径,因为最短路径事实上最多有n-1条边,所以这种松弛会进行n-1操作。最终,我们得到就是从源点0出发,经过给出的m条边,到各个点的最短路。此时的算法复杂度为O(n*m)。

接下来,我们思考来如何优化一下

我们真的需要进行n-1松弛吗?事实上,如果在某一轮松弛中,dis数组没有发生任何更新,那么在这次松弛之后所有的松弛也一定不会改变dis数组,所以后面所有的循环都是没有必要的,因此,我们可以加入一个flag,来判断这一轮松弛是否更新了dis数组,如果没有更新,直接跳出即可。具体代码实现如下:

 for(int k = ;k<=n-;k++)
{
bool flag = true;
for(int i = ;i<=m;i++)
if(dis[v[i] > dis[u[i] + w[i])
{
dis[v[i]] = dis[u[i]] + w[i];
flag = false;
}
if(flag)
break;
}

接下来,我们继续来想,什么时候dis[v[i]]的值可能会发生改变,从上面给出的式子来说,因为w[i]是根据输入定的,所以不可改变,所以,只有当dis[u[i]]的值发生改变时,dis[v[i]]的值才可能会发生改变,那么也就是说,只有当与dis值改变的点的点的dis值才有可能改变,有点绕,可能需要思考一段时间。所以我们可以把每次松弛操作中dis值改变的点放在集合中,下次只对与这些点相连的点进行松弛,必将这些点放进集合中,但是重复的点不应该出现在集合中,所以我们要有一个vis数组判断当前这个点是否在集合中。代码实现如下(此处用邻接表存的边,vec[u][i].to表示有一条从u指向它的边,权值为vec[u][i].val):

  queue<int>q;
q.push();
vis[] = ;
while(!q.empty())
{
int d = q.front();
q.pop();
vis[d] = ;
for(int i = ;i<vec[d].size();i++)
{
if(dis[vec[d][i].to]>dis[d]+vec[d][i].val)
{
dis[vec[d][i].to]=dis[d]+vec[d][i].val;
if(!vis[vec[d][i].to])
{
vis[vec[d][i].to] = ;
q.push(vec[d][i].to);
} }
}
}

这也就是SPEA算法的真正核心代码。

接下来我们来思考如何判断是否存在负权回路,假设一个图中存在一个负权回路,那这个图中是不存在最短路的,因为每走一次负权回路,源点到某些点的距离一定会改变的。对应上面代码,就会出现死循环,因为有些点的dis值会一直改变,也就是有些点会进入队列很多次,这是一个突破口,那么一个点到底进入队列多少次算是存在负权回路那,思考最开始说的,图的最短路最多有n-1条边,所以,一个点最多应该进入队列n-1次,因此,当一个进入队列的次数大于等于n次时,该图中就存在负权回路。因此我们只要在上面代码中加上这一条件的判断就能判断是否存在负权回路了,最后给出本题的ac代码。

 #include <cstdio>
#include <queue>
#include <vector>
using namespace std;
const int maxn = +;
const int INF = 0x3f3f3f3f;
struct V
{
int to,val;
V(int a,int b):to(a),val(b){}
};
vector<V>vec[maxn];
int vis[maxn],dis[maxn],num[maxn];
int n,m;
void init()
{
for(int i = ;i<n;i++)
{
vec[i].clear();
vis[i] = ;
num[i] = ;
dis[i] = INF;
}
dis[] = ;
}
bool check()
{
queue<int>q;
q.push();
vis[] = ;
num[]++;
while(!q.empty())
{
int d = q.front(); q.pop();
vis[d] = ;
for(int i = ;i<vec[d].size();i++)
{ //printf("d = %d,%d,%d\n",d,dis[vec[d][i].to],dis[d]+vec[d][i].val);
if(dis[vec[d][i].to]>dis[d]+vec[d][i].val)
{
dis[vec[d][i].to]=dis[d]+vec[d][i].val;
if(!vis[vec[d][i].to])
{
num[vec[d][i].to]++;
vis[vec[d][i].to] = ;
q.push(vec[d][i].to); if(num[vec[d][i].to]>=n)
{
return true;
}
} }
}
}
return false;
}
int main()
{
//freopen("out.txt","w",stdout);
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d %d",&n,&m);
init();
int a,b,c;
for(int i = ;i<m;i++)
{
scanf("%d %d %d",&a,&b,&c);
vec[a].push_back(V(b,c));
//vec[b].push_back(V(a,c));
}
if(check())
printf("possible\n");
else
printf("not possible\n");
}
return ;
}

本文结束....

  

  

UVA - 558 Wormholes (SPEA算法模板题)的更多相关文章

  1. hdu 1711 KMP算法模板题

    题意:给你两个串,问你第二个串是从第一个串的什么位置開始全然匹配的? kmp裸题,复杂度O(n+m). 当一个字符串以0为起始下标时.next[i]能够描写叙述为"不为自身的最大首尾反复子串 ...

  2. POJ 3041 匈牙利算法模板题

    一开始预习是百度的算法 然后学习了一下 然后找到了学长的ppt 又学习了一下.. 发现..居然不一样... 找了模板题试了试..百度的不好用 反正就是wa了..果然还是应当跟着学长混.. 图两边的点分 ...

  3. uva 558 - Wormholes(Bellman Ford判断负环)

    题目链接:558 - Wormholes 题目大意:给出n和m,表示有n个点,然后给出m条边,然后判断给出的有向图中是否存在负环. 解题思路:利用Bellman Ford算法,若进行第n次松弛时,还能 ...

  4. poj 1274 The Perfect Stall【匈牙利算法模板题】

    The Perfect Stall Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 20874   Accepted: 942 ...

  5. Bellman-Ford算法模板题

    POJ 3259 虫洞(Bellman-Ford判断有无负环的问题) 描述: 在探索他的许多农场时,Farmer John发现了许多令人惊叹的虫洞.虫洞是非常奇特的,因为它是一条单向路径,在您进入虫洞 ...

  6. POJ 1459 Power Network(网络最大流,dinic算法模板题)

    题意:给出n,np,nc,m,n为节点数,np为发电站数,nc为用电厂数,m为边的个数.      接下来给出m个数据(u,v)z,表示w(u,v)允许传输的最大电力为z:np个数据(u)z,表示发电 ...

  7. UVA 10820 欧拉函数模板题

    这道题就是一道简单的欧拉函数模板题,需要注意的是,当(1,1)时只有一个,其他的都有一对.应该对欧拉函数做预处理,显然不会超时. #include<iostream> #include&l ...

  8. UVA 796 Critical Links(模板题)(无向图求桥)

    <题目链接> 题目大意: 无向连通图求桥,并将桥按顺序输出. 解题分析: 无向图求桥的模板题,下面用了kuangbin的模板. #include <cstdio> #inclu ...

  9. HDU - 2255 奔小康赚大钱 KM算法 模板题

    HDU - 2255 题意: 分配n所房子给n个家庭,不同家庭对一所房子所需缴纳的钱是不一样的,问你应当怎么分配房子,使得最后收到的钱最多. 思路: KM算法裸题.上模板 #include <i ...

随机推荐

  1. 让input不可编辑

    有时候,我们希望表单中的文本框是只读的,让用户不能修改其中的信息,如使<input type="text" name="input1" value=&qu ...

  2. 初识HT for web

    目前国内经济转型在潜移默化中已经发生了巨大的变化,保险,零售业,汽车等我能想到的. 只要互联网能插足的行业,都难逃一‘劫’. 刚看了一篇博客--基于 HTML5 的工业组态高炉炼铁 3D 大屏可视化 ...

  3. sqlparameters

    1.数据访问层 using的用法: 01.可以using System;导命名控空间 02.using 的语法结构 using(变量类型  变量名 =new 变量类型()) { } 案例: 03.us ...

  4. Linux的常识

    用到$是环境变量查询的开头 # echo $LANG查看编码 ls -l 是查看本地的所有文件的目录 以list的形式罗列出来 cd .. 上一层的目录 查看当前目录下有哪些文件 ll 等于ls -l ...

  5. CSS可见区域全局居中

    top:$(document).scrollTop() + ($(document).height() - $(document).scrollTop())/2,

  6. Python爬虫入门之Urllib库的高级用法

    1.设置Headers 有些网站不会同意程序直接用上面的方式进行访问,如果识别有问题,那么站点根本不会响应,所以为了完全模拟浏览器的工作,我们需要设置一些Headers 的属性. 首先,打开我们的浏览 ...

  7. Java中的静态和枚举

    一.静态成员 对静态成员最简单的解释,静态成员属于整个类而不属于某个对象,所以又叫做类变量.一个类不管创建多少个实例对象,静态变量在内存中有且只有一个(调用方法用类名调用). 通常的非静态变量称为实例 ...

  8. 关于Django的网页编写

    关于Django的网页编写 一. 模型 模型是Django项目的数据唯一的.权威的信息源,他包含你所存储数据的必要字段,通常每个模型对应数据库中卫衣的一张表.每一个模型都是django.db.mode ...

  9. 表单传值给@Controller

    <form action="springmvc/testModelAttributes" method="post"> <input type ...

  10. linux - man 提示:No entry for tty in section 4 of the manual

    在使用man手册时,出现空章节的情况: 如: 原因:在CentOS6.5安装时,采用了minimal方式安装,许多包都没有安装上,man手册的man-pages包也没有安装 解决方案:安装man-pa ...