UVA - 558 Wormholes (SPEA算法模板题)
先给出题面: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算法模板题)的更多相关文章
- hdu 1711 KMP算法模板题
题意:给你两个串,问你第二个串是从第一个串的什么位置開始全然匹配的? kmp裸题,复杂度O(n+m). 当一个字符串以0为起始下标时.next[i]能够描写叙述为"不为自身的最大首尾反复子串 ...
- POJ 3041 匈牙利算法模板题
一开始预习是百度的算法 然后学习了一下 然后找到了学长的ppt 又学习了一下.. 发现..居然不一样... 找了模板题试了试..百度的不好用 反正就是wa了..果然还是应当跟着学长混.. 图两边的点分 ...
- uva 558 - Wormholes(Bellman Ford判断负环)
题目链接:558 - Wormholes 题目大意:给出n和m,表示有n个点,然后给出m条边,然后判断给出的有向图中是否存在负环. 解题思路:利用Bellman Ford算法,若进行第n次松弛时,还能 ...
- poj 1274 The Perfect Stall【匈牙利算法模板题】
The Perfect Stall Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 20874 Accepted: 942 ...
- Bellman-Ford算法模板题
POJ 3259 虫洞(Bellman-Ford判断有无负环的问题) 描述: 在探索他的许多农场时,Farmer John发现了许多令人惊叹的虫洞.虫洞是非常奇特的,因为它是一条单向路径,在您进入虫洞 ...
- POJ 1459 Power Network(网络最大流,dinic算法模板题)
题意:给出n,np,nc,m,n为节点数,np为发电站数,nc为用电厂数,m为边的个数. 接下来给出m个数据(u,v)z,表示w(u,v)允许传输的最大电力为z:np个数据(u)z,表示发电 ...
- UVA 10820 欧拉函数模板题
这道题就是一道简单的欧拉函数模板题,需要注意的是,当(1,1)时只有一个,其他的都有一对.应该对欧拉函数做预处理,显然不会超时. #include<iostream> #include&l ...
- UVA 796 Critical Links(模板题)(无向图求桥)
<题目链接> 题目大意: 无向连通图求桥,并将桥按顺序输出. 解题分析: 无向图求桥的模板题,下面用了kuangbin的模板. #include <cstdio> #inclu ...
- HDU - 2255 奔小康赚大钱 KM算法 模板题
HDU - 2255 题意: 分配n所房子给n个家庭,不同家庭对一所房子所需缴纳的钱是不一样的,问你应当怎么分配房子,使得最后收到的钱最多. 思路: KM算法裸题.上模板 #include <i ...
随机推荐
- python中pyperclip库的功能
python3中pyperclip库的功能 作用就是复制.粘贴 例子 import pyperclip pyperclip.copy('Hello world!') pyperclip.paste() ...
- linux常用命令 grep命令
linux grep命令 Linux系统中grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配行打印出来 grep 全称 Grobal Regular Expression Pr ...
- CentOS 7 常用命令大全
CentOS7 常用命令集合 这两天一直在对CentOS 7.2进行初体验,各种学习命令肿么用,不过其实大多和DOS是一样的,只是命令的表达上可能有点儿不一样,毕竟这些都不是一家出来的嘛~ 废话不多说 ...
- flask基础--第二篇
1.Flask中的HTTPResponse,Redirect, render #导入render_template和redirect from flask import Flask,render_te ...
- Winsock API编程介绍
相信很多人都对网络编程感兴趣,下面我们就来介绍,在网络编程中应用最广泛的编程接口Winsock API. 使用Winsock API的编程,应该了解一些TCP/IP的基础知识.虽然你可以直接使用Win ...
- yii防止延迟用户多次点击按钮重复提交数据
是不是被用户的行为所困扰? 一. 一个表单用户点击提交按钮了N次,这也导致了数据提交了N次. 为了此受到了测试的欺辱,受到了老板的批评? 不用怕,它就是来拯救你的. 第一步:打开命令行,敲入 comp ...
- javascript中的add(1)(2)(3)(4)是怎么实现的
javascript中的add(1)(2)(3)(4)是怎么实现的?实现如下: var fn = function(a){ let sum = a; let tempFn = function(b){ ...
- web网页错误代码的含义
web网页错误代码的含义 一.1xx 表示临时响应并需要请求者继续进行操作的状态码,例如: 100--继续:101--切换协议 二.2xx 表示服务器成功的处理了请求的状态码,例如: 200--客户端 ...
- Tensorflow实战系列之二:
还没想好,可能是人脸检测或者物体检测,或者加上动态检测~~
- docker学习端口连接docker容器---第四章节
一.Docker容器连接 前面的第二章节,我们事先通过网络端口来访问运行在docker容器内的服务,我们也可以通过端口连接到一个docker容器 我们可以指定容器绑定的网络地址,如绑定127.0.0. ...