Querainy
2021-05-12 16:48:02
最短路算法Dijkstra大家都知道!
今天我们来说一说,怎么用基数堆+Fib堆把Dijkstra优化到
译自Faster Algorithms for the Shortest Path Problem。
翻译这个的原因是学校要带英文读物,然后我就带了这个论文......下星期可能还会看新的东西/cy
更新了代码,在最后。
众所周知Dij最短路不弱于排序,所以我们有了一个
优化Dij,我们的数据结构要做的无非是三件事情 :
insert(插入)
decrease-key(减值)
extract-min(查询最小值并删除)
Dij本身也有一些性质,我们主要用到的是 : 每次extract-min,取出的最小值是单调递增的。
基数堆开了一排桶来存所有的权值。
对于第
然后定义
为了支持快速的插入删除,我们用双链表维护每个桶里面的元素。
接下来我们已经可以实现
decrease-key可以直接重新插入,这是
你说insert的均摊复杂度?它会增加
接下来是重头戏extract-min了。我们首先找到第一个非空桶,如果这个桶就是
你发现暴力找最小值可能达到
爆破 这个翻译只是觉得通俗并且读起来舒服(
怎么重新分配
你发现根据刚才分配桶大小的方式,第
使用上面的数据结构,可以得到一个
直译的。叫 线段基数堆 好像也很有道理?
基数堆慢在哪里?桶个数太多了,这让我们没法快速找到第一个非空桶。
考虑不要
这么大的桶还不是很好,我们再把桶划分成线段,每个桶分成
我们记
桶的右端点和区间分配方式跟普通的基数堆一样,但是我们如何分配线段的区间?
这里我们从桶的右端点开始,从右往左摆放线段。比如一个桶
右边三段每一段长度都是
注意尽管我们从右往左摆放线段,线段还是从左往右编号的。
这样做有什么好处?插入
预处理
这样我们就有一个
然后是extract-min,我们还是找到第一个非空桶
注意到一条线段的大小恰好是它前一个桶的大小,那么一条线段里的元素仍然一定可以全部扔进前面的桶里去,这可以保证我们的复杂度是找到第一个非空桶和第一个非空线段的复杂度
这样我们的总复杂度就是
如果你第一遍没有读懂这一部分,请坚持读到最后并再读一遍。
这个找桶+找线段还是太慢了,居然要达到
考虑用一种数据结构来维护这个找线段的过程。给所有线段从左往右编号,我们在Dijkstra中对每个点维护它所在的线段,那么找第一个非空线段就是找这些点对应线段中的编号最小值。好像又看到了一个堆问题?
这个新的堆问题有一个好处 : 我们只有
当然使用Fib堆。对于权值只有
我们对于每种权值开一个桶,并选一个代表元。
称Fib堆中一个结点是激活的(active),当且仅当它存的是代表元;否则它是未激活的(passive)。激活的结点显然最多只有
称Fib堆中一棵树是激活的,当且仅当它的根是激活的;否则它是未激活的。同时维护两个根链表,分别存激活和未激活的树。这个新的Fib堆保持一个性质 : 所有未激活结点都是根。
为了简单,我们定义 激活一个根 为把这个根设为激活的,并把它的树插入激活的根链表。不激活一个根 类似。对于不是根的点这个操作没有定义。根据上面的性质,我们立刻可以得出 : 激活一个未激活结点一定是有定义的。
保留Fib堆的势函数不变。
insert一个
decrease-key一个
然后直接按照Fib堆的方法做。
然后检查是否已经有值为新的
上面两个操作的均摊复杂度显然还是
extract-min时,我们在激活的树中查找
因为激活的结点最多只有
好了我们回来拿这个Fib堆优化基数堆。
还记得刚才说到哪里了吗?为了优化双层基数堆,我们把线段从左往右编号,在Dij中我们维护每个点所在的线段编号,那么找第一个非空线段就变成了找这些编号中的最小值。线段编号只有最多
这样剩下的就只有复杂度平衡的工作了。insert均摊复杂度是
让我们看看,
空间复杂度是
这个来自论文最后一节,不过作者发明这个东西本来不是为了要简单地实现它......
考虑基数堆有点难写,主要原因就是我们需要维护每个桶的
不过考虑到基数堆一定程度上是基于二进制的,我们可以干一件很有意思的事情。设最小值是
insert一个
extract-min也很简单。首先从左往右扫到的第一个非空桶肯定包含
然后要搬到双层基数堆和它的Fib堆优化上,那就直接把二进制异或换成
不过问题在于,不管这样的基数堆多么容易实现,谁会愿意去写Fib堆优化的部分呢?
谢谢朋友们!
在很多年以后的2022-03-07,我为cf1520G写了一个基数堆优化的dij。这是这道题的代码,可以卡着时限跑过(正解是线性的),速度大概是4e6点 3e7边 2.8s,不过由于这题是网格图可能不是很满。最重要的常数优化就是一上来给每个temp reserve到1e6,然后在push_back的时候可以快一万倍。感觉比链表要快一万倍,但是如果可以开
#include<stdio.h>
#include<vector>
#include<string.h>
#include<iostream>
using std::cin;
using std::cout;
using std::vector;
int n,m,w,p,a[2002][2002];
#define node(i,j) (((i)-1)*m+(j))
#define inv_node_x(x) (((x)-1)/m+1)
#define inv_node_y(x) (((x)-1)%m+1)
long long dis[4000010];
bool vis[4000010];
struct DijRadixHeap
{
#define highbit(x) ((x)?64-__builtin_clzll(x):0)
vector<int> b[60],temp[60];
int cur,c[60],c0p;
int p[4000010];
inline void build(int n,int s)
{
cur=s;
for(int i=1;i<=n;i++)
c[highbit(dis[i]^dis[s])]++,b[highbit(dis[i]^dis[s])].push_back(i),p[i]=b[highbit(dis[i]^dis[s])].size()-1;
for(int i=0;i<=55;i++) temp[i].reserve(1000000);
}
inline int top(){ return cur; }
inline void pop()
{
static int _c[60];
int last=cur;c[0]--,p[cur]=-1,cur=0;
if(c[0]){ while(p[b[0][c0p]]==-1) c0p++; cur=b[0][c0p]; return; }
for(int i=1;i<=55;i++) if(c[i])
{
for(int j=0;j<b[i].size();j++)
if(highbit(dis[b[i][j]]^dis[last])==i&&p[b[i][j]]==j&&dis[b[i][j]]<dis[cur]) cur=b[i][j];
for(int j=0;j<=i;j++) c[j]=0;
for(int j=0;j<=i;j++) temp[j].reserve(c[j]),temp[j].resize(0);
for(int j=0,t;j<=i;j++)
for(int k=0;k<b[j].size();k++)
if(highbit(dis[b[j][k]]^dis[last])==j&&p[b[j][k]]==k)
t=highbit(dis[b[j][k]]^dis[cur]),temp[t].push_back(b[j][k]),c[t]++,p[b[j][k]]=temp[t].size()-1;
for(int j=0;j<=i;j++) swap(temp[j],b[j]);
c0p=0;
break;
}
}
inline void decrease(int u,long long new_d)
{
c[highbit(dis[u]^dis[cur])]--,c[highbit(new_d^dis[cur])]++,
b[highbit(new_d^dis[cur])].push_back(u),p[u]=b[highbit(new_d^dis[cur])].size()-1;
}
}q;
inline void dij(int _n,int s)
{
dis[0]=1e16;
for(int i=1;i<=_n;i++) dis[i]=1e16-1e9;
dis[s]=0,q.build(_n,s);
for(int T=1;T<=_n;T++)
{
int u=q.top();
if(vis[u]) continue;
vis[u]=1;
if(u==n*m) return;
if(u==p)
{
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(a[i][j]>0&&dis[u]+a[i][j]<dis[node(i,j)]) q.decrease(node(i,j),dis[u]+a[i][j]),dis[node(i,j)]=dis[u]+a[i][j];
}
else
{
int i=inv_node_x(u),j=inv_node_y(u);
if(i<n&&a[i+1][j]!=-1&&dis[u]+w<dis[node(i+1,j)]) q.decrease(node(i+1,j),dis[u]+w),dis[node(i+1,j)]=dis[u]+w;
if(j<m&&a[i][j+1]!=-1&&dis[u]+w<dis[node(i,j+1)]) q.decrease(node(i,j+1),dis[u]+w),dis[node(i,j+1)]=dis[u]+w;
if(i>1&&a[i-1][j]!=-1&&dis[u]+w<dis[node(i-1,j)]) q.decrease(node(i-1,j),dis[u]+w),dis[node(i-1,j)]=dis[u]+w;
if(j>1&&a[i][j-1]!=-1&&dis[u]+w<dis[node(i,j-1)]) q.decrease(node(i,j-1),dis[u]+w),dis[node(i,j-1)]=dis[u]+w;
if(a[i][j]>0&&dis[u]+a[i][j]<dis[p]) q.decrease(p,dis[u]+a[i][j]),dis[p]=dis[u]+a[i][j];
}
q.pop();
}
}
int main()
{
std::ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>m>>w,p=n*m+1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) cin>>a[i][j];
dij(n*m+1,node(1,1));
cout<<(dis[node(n,m)]==1e16-1e9?-1:dis[node(n,m)]);
return 0;
}