SPFA已死,有事烧纸
其实我本人也是一个SPFA的忠诚用户,至少我的最少费用最大流都是用SPFA打的。但是,就在最近,发生了一个天大的丑闻!一个大佬竟将SPFA卡死!!!还有千千万万的SPFA站起来!!!
不过SPFA其实还是可以用来拿一定的分数,比如一些很神奇的优化。
这时,我们的 d i j k s t r a dijkstra dijkstra便一下子火了!
而且 d i j k s t r a dijkstra dijkstra也可以加优化,最快可以到达 O ( n l o g n + m ) O(nlogn+m) O(nlogn+m),吊打SPFA?
Dijkstra
注:Dijkstra不能处理负边的情况。
首先,我们有四个点,1号点的点权为0,那么我们就用他更新其他的点。
那么现在在白色点里面,4号点是最小的,我们继续用4号点更新。
继续:
那么我们就可以得到一段代码。
bool bk[21000];
int d[21000];
d[0]=inf;
for(int i=1;i<n;i++)
{
int id=0;
for(int i=1;i<=n;i++)
{
if(!bk[i] && d[i]<d[id])id=i;
}//找最小
bk[id]=true;
for(int k=last[id];k;k=a[k].next)
{
int c=a[k].c;//边权
if(d[a[k].y/*边的另外一个终点*/]>d[id]+c)d[a[k].y]=d[id]+c;
}
}
当然,这种做法也是显而易见的正确了,在无负边的情况下当前距离最短的没被选过的点肯定不能被其他没被选过的点更新,实则就是加了贪心思想。
但是因为裸写的复杂度为 O ( n 2 + m ) O(n^{2}+m) O(n2+m),在找最小值方面较慢,所以也不行,需要用数据结构优化。
配对堆
引言
这里的配对堆不是pb_ds里面的!!!!!!!!!!!!!!!!!!!!
在无尽地堆海世界里面,有个叫斐波纳契堆的玩意,修改插入 O ( 1 ) O(1) O(1),弹出堆顶 O ( l o g n ) O(logn) O(logn),而二项堆则较为鸡肋,而这两个常数都大的一匹(代码也长)!!!
而二叉堆、优先队列(STL,开O2的话配对堆的时间和他差不了多少)、zkw线段树(不是堆,但这个常数真的小,配对堆跟他比还是慢一点),但是由于修改为 O ( l o g n ) O(logn) O(logn),导致时间复杂度变为 O ( n + m l o g n ) O(n+mlogn) O(n+mlogn)(优先队列的一般写法是 O ( n + l o g m ) O(n+logm) O(n+logm),因为不支持修改),但是常数小。
有没有综合一点的一个数据结构呢?
这是,配对堆便闪闪发光了,因为代码简洁和具备斐波纳契堆一样的复杂度,以及常数较小,被世人所赞叹,不过,还是存在许多不确定性的,毕竟没有完美的事物。
讲解
配对堆实质上是一棵树,用边目录进行连接
由于这个写的是排序,所以就比较简单,但是在优化Dij时因为要修改,为防止内存大和速度快,改了一些内容,当然,等你学会了排序后,这个差不多就会了。
下面讲
合并
两个堆的堆头,哪个小哪个为根,另外一个认他为父亲,就这么粗暴!!!
inline int merge(int x,int y){
v[x]>v[y]?(x^=y^=x^=y):0;inz(x,y);return x;}
修改
就是我与父亲断绝关系,然后我修改我自己的值,将我与我的子树与root合并。
按我的理解是 O ( 1 ) O(1) O(1),而且对后面 p o p pop pop的影响也只是 1 1 1或 2 2 2次的影响,不知道为什么官方说是 O ( l o g n ) O(logn) O(logn),当然,此篇文章将按 O ( 1 ) O(1) O(1)来算。
当然,这样只能改小,不能改大,改大的话可以断绝关系后pop一下然后再合并回来, O ( l o g n ) O(logn) O(logn)的时间。
void change(int id,LL x){v[id]=x;fa[id]=0;root=(root==id?id:merge(root,id));/*这条边就浪费了*/}//改变边权,这题不用
当然,边就浪费了,在后面会有一个回收的版本,但是慢一点,内存会小。
弹出堆顶pop
这么随便的一个数据结构为什么复杂度这么优秀,全看核心操作,pop(),将堆顶的所有儿子按一定顺序合并起来,然后弹出堆顶,而合并的顺序也是有讲究的,不是一个接一个,而是两两合并,这样每个点的儿子最多只有 l o g n logn logn个,而配对堆时间复杂度最优秀的时候则是一条链的时候,排序都排序好了!
给个图好理解&