dij-spfa乱搞
以前见过一篇另类堆优化dij的题解,然而找不到了
那位作者称它为dij-spfa(大概是这个意思,然而确实很形象
这方法比较玄学,正确性没有严格证出来,然而对拍是验证猜想的最好途径
不过也可能并不玄学,只是我一时没想出来而已,如果有见解可以发在评论
还有一个关于这个的讨论帖
众所周知,dij堆优的代码是这样,用了STL:
#include<bits/stdc++.h>
#define LL long long
using namespace std;
struct node{
int w;
LL v;
bool operator < (const node& x) const{
return v>x.v;
}
};
int n,m,s;
LL dis[100006],vis[100006];
int fir[100006],nex[200006],to[200006],v[200006];
priority_queue<node>q;
void dij(){
memset(dis,0x3f,sizeof dis);
dis[s]=0;
q.push((node){s,0});
while(q.size()){
int u=q.top().w;q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=fir[u];i;i=nex[i]){
int vv=to[i];
if(dis[vv]>dis[u]+v[i]){
dis[vv]=dis[u]+v[i];
q.push((node){vv,dis[vv]});
}
}
}
}
int main(){
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=m;i++){
int x,y,z;scanf("%d%d%d",&x,&y,&z);
to[i]=y;v[i]=z;
nex[i]=fir[x];
fir[x]=i;
}
dij();
for(int i=1;i<=n;i++) printf("%lld ",dis[i]);
}
思考一下这个重载运算符:
struct node{
int w;
LL v;
bool operator < (const node& x) const{
return v>x.v;
}
};
和这个入队操作:
if(dis[vv]>dis[u]+v[i]){
dis[vv]=dis[u]+v[i];
q.push((node){vv,dis[vv]});
}
每次入队,都是把节点编号和当前节点距离做成结构体
然而,在这个结构体在优先队列的期间,这个\(dis\)的值可能会变(被松弛了),然而这并不影响结构体里的值
那么,我们可不可以只让节点号入队,然后比较节点的\(dis\)
然后,再结合一点spfa,给每个点记录是否在队中,如果在,就只松弛不入队
那么稍加改动可以写出这样的代码,当然这里我优先队列是手写堆实现的,\(vis\)记录的是是否在对中:
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<queue>
#include<stack>
#include<iomanip>
#include<cstring>
#define R register
#define LL long long
inline int read(){
int x=0,y=1;
char c=getchar();
while(c<'0'||c>'9'){
if(c=='-') y=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=x*10+(c^48);
c=getchar();
}
return x*y;
}
int n,m,s;
int to[200006],net[200006],v[200006];
int tot;
int fir[100006],dis[100006];
bool vis[100006];
int dui[100006],sz;
void swapn(int &ii,int &jj){
ii^=jj;
jj^=ii;
ii^=jj;
}
int minnpop(){
int res=dui[1];
dui[1]=dui[sz];
dui[sz]=0;
sz--;
int i=1,L,r;
while(i*2<=sz){
L=i*2;
r=L+1;
if(r<=sz&&dis[dui[r]]<dis[dui[L]]) L=r;
if(dis[dui[i]]<=dis[dui[L]]) break;
swapn(dui[i],dui[L]);
i=L;
}
return res;
}
void add(int x){
dui[++sz]=x;
int i=sz,p;
while(i>1){
p=i/2;
if(dis[dui[p]]<=dis[dui[i]]) break;
swapn(dui[p],dui[i]);
i=p;
}
}
void dijkstra(){
memset(dis,66,sizeof(dis));
dis[s]=0;
add(s);
vis[s]=true;
int st,now;
while(sz){
st=minnpop();
vis[st]=false;
for(R int i=fir[st];i;i=net[i]){
now=to[i];
if(dis[now]>dis[st]+v[i]){
dis[now]=dis[st]+v[i];
if(!vis[now]){
add(now);
vis[now]=true;
}
}
}
}
}
int main(){
n=read();
m=read();
s=read();
int sta,e;
for(R int i=1;i<=m;i++){
sta=read();
e=read();
v[++tot]=read();
to[tot]=e;
net[tot]=fir[sta];
fir[sta]=tot;
}
dijkstra();
for(R int i=1;i<=n;i++) printf("%d ",dis[i]);
return 0;
}
这种方法看似很乱搞,其实是能过的
为什么说他乱搞?因为可以看出:
if(dis[dui[p]]<=dis[dui[i]]) break;
这是堆操作的代码,我们\(dui\)数组存的是节点编号,比较规则是比较当期节点的\(dis\)
然而,每经过一次松弛,\(dis\)都会变,但此时我们并不会刻意去调整堆结构
所以堆就乱了
那么它为啥还能过?个人认为是:在频繁的push
和pop
中,恰好将堆的性质调整好了
因为只有目前不在堆中的节点才能入堆,所以堆中元素最多\(n\)个,让堆操作的复杂度大大减小,可能也是因为元素少了,堆性质更容易维护才使这个算法没有挂掉
回想一下第一种,只要当前节点被松弛了,就接着扔到堆里,之后每个节点只能被用来松弛其它节点一次,已经被用过的话就直接扔掉
但这个是,只要堆里有节点,不管它有没有别用去松驰过,都继续用它松弛(spfa的影子)
可能,除了堆操作以外的正确性,就是用这一点来保证的
目前,用这种方法写最短路的题,还没有挂掉过
并且,经过了@Nickel_Angel大佬的几百次\(n \leq 1000,m \leq 10^5\)的对拍,也没挂,并且感谢她,当时发那个讨论帖的时候我还不会对拍
而且,它更快!
普通版,800ms+
优化后,200ms+
用时是之前的四分之一
主要节省时间的地方,应该就是刚才说的堆的复杂度降低
而且在洛谷标准版最短路这样卡spfa的数据下,dij-spfa能跑出这样的时间,说明这个算法在用时上也是比较稳定
然而我用这个方法去做bzoj的那个需要用斐波那契堆或配对堆优化的dij,还是不能过
最后,最前面两个完整代码是我去年写的,后面那个是刚写,看起来不大一样
如果您有hack数据,或者严谨的证明,欢迎在评论区给出,感谢
dij-spfa乱搞的更多相关文章
- 【洛谷4009】汽车加油行驶问题(SPFA乱搞)
点此看题面 大致题意:给定一个\(N*N\)的方形网格,其中1表示这个格子有油库,0表示这个格子没油库,且汽车加满油可以行驶\(k\)条网格边.如果遇到油库必须加满油并花费\(A\)元,如果\(X\) ...
- bzoj 1050: [HAOI2006]旅行comf(codevs.cn 1001 舒适的路线) 快排+并查集乱搞
没用的话:好像很久没发博客了,主要是懒太蒟找不到水题.我绝对没弃坑...^_^ 还用些话:本文为博主原创文章,若转载请注明原网址和作者. 进入正题: 先pa网址: bzoj :http://www.l ...
- BZOJ-1834 网络扩容 最小费用最大流+最大流+乱搞
1834: [ZJOI2010]network 网络扩容 Time Limit: 3 Sec Memory Limit: 64 MB Submit: 2269 Solved: 1136 [Submit ...
- 【51nod1443】路径和树(堆优化dijkstra乱搞)
点此看题面 大致题意:给你一个无向联通图,要求你求出这张图中从u开始的权值和最小的最短路径树的权值之和. 什么是最短路径树? 从\(u\)开始到任意点的最短路径与在原图中相比不变. 题解 既然要求最短 ...
- URAL 1827 Indigenous Wars(排序、乱搞)
题意:给一个长度为n数组{a[i]}.有m个操作Ti,Si,Li表示找以Ti值结束,以Si值开始,长度为Li的连续子串.找到后,将区间的答案值设为1.一开始答案值全部为0.最后输出n个答案值. 好久没 ...
- UVA 11853 [dfs乱搞]
/* 大连热身E题 不要低头,不要放弃,不要气馁,不要慌张 题意: 在1000×1000的格子内有很多个炮弹中心,半径给定. 为某人能否从西部边界出发,从东部边界走出. 不能输出不能,能的话输出最北边 ...
- Codeforces 732e [贪心][stl乱搞]
/* 不要低头,不要放弃,不要气馁,不要慌张 题意: 给n个插座,m个电脑.每个插座都有一个电压,每个电脑都有需求电压. 每个插座可以接若干变压器,每个变压器可以使得电压变为x/2上取整. 有无限个变 ...
- 【BZOJ-4692】Beautiful Spacing 二分答案 + 乱搞(DP?)
4692: Beautiful Spacing Time Limit: 15 Sec Memory Limit: 128 MBSubmit: 46 Solved: 21[Submit][Statu ...
- 【BZOJ-3578】GTY的人类基因组计划2 set + map + Hash 乱搞
3578: GTY的人类基因组计划2 Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 367 Solved: 159[Submit][Status][ ...
随机推荐
- Flask 入门(特别篇)
作为一款优秀的编辑器,pycharm得到了很多人的支持,但是刚接触它的小伙伴会遇到一个困难,如何把一个别人做的python项目导入到pycharm里面呢? 1.手动建立一个虚拟环境,注意这个环境和你导 ...
- idea运行gradle项目报错,找不到符号符号,方向xxxx类未知
报错: 解决:把build和run设置为idea
- python常用算数运算符、比较运算符、位运算符与逻辑运算符
编辑时间: 2019-09-04,22:58:49 算数运算符 '+'.'-'.'*'.'/' :加.减.乘.除 '**':指数运算, ‘//’:整除, ‘%‘:求余数 num_1 = 15; num ...
- python3(二十五) getClassInfo
""" """ __author__ = 'shaozhiqi' # 如何知道这个对象是什么类型,使用type() print(type(1 ...
- 《深入理解 Java 虚拟机》读书笔记:垃圾收集器与内存分配策略
正文 垃圾收集器关注的是 Java 堆和方法区,因为这部分内存的分配和回收是动态的.只有在程序处于运行期间时才能知道会创建哪些对象,也才能知道需要多少内存. 虚拟机栈和本地方法栈则不需要过多考虑回收的 ...
- web自动化测试中的PO模式(一)
1.PO模式的思想 原理: 将页面的元素定位和元素行为封装成一个page类 类的属性:元素的定位 类的行为:元素的操作 页面对象和测试用例分离 测试用例: 调用所需要页面对象中的行为,组成测试用例 测 ...
- 【MyBatis深入剖析】应用分析与最佳实践
##### 文章目标1. 了解ORM框架的发展历史,了解MyBatis特性2. 掌握MyBatis编程式开发方法和核心对象3. 掌握MyBatis核心配置含义4. 掌握MyBatis的高级用法与扩展方 ...
- Category、load、initialize 源码讲解
今天深圳天气有暴风雨,没有事情干,趁着周末和平常晚上写一篇关于Category知识的梳理!可能针对平常只会知道些category基本结论知道的人有些帮助,写这篇博客会按照下面的目录结合实例以及Cate ...
- ADO.NET(二)
对Command的拓展延伸 执行SQL语句. Command 对象需要取得将要执行的SQL语句,通过调用该类的多种方法,向数据库提交SQL语句. ExecuteNonQuery(),ExecuteR ...
- 可以用 Python 编程语言做哪些神奇好玩的事情?除了生孩子不能,其他全都行!
坦克大战 源自于一个用Python写各种小游戏的github合集,star数1k.除了坦克大战外,还包含滑雪者.皮卡丘GOGO.贪吃蛇.推箱子.拼图等游戏. 图片转铅笔画 帮助你快速生成属于自己的铅笔 ...