dij告诉你拿金最短的路
dijk求最小路
这是我的一个接触的最短路算法,在之前看到了SPFA已死的多种言论之后我决定想去学dij,这个算法就类似于SPFA加了一个堆的优化,有一点贪心的思想在里面,其实本质上还是松弛,我们假设要找的点是x,那么我们先去找x所有的路,去更新dis,(注意dis最开始赋值为无穷大),之后入堆,我们每次拿出堆顶的的一个去松弛她所连接的点,之后知道所有都完成为止,其实不难,注意这个不可以去求有负环的最短路,如果有就还是用已经死掉(bushi)的SPFA
最后模板来一手
int dis[10000];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;//造一个小根堆,另外一个存是那个点
void dij(int x){
for(int i=1;i<=n;i++)dis[i]=oo;
dis[x]=0;
q.push(pair<int,int>(dis[x],x));
while(!q.empty()){
while(!q.empty()&&q.top().first>dis[q.top().second]) q.pop();//如果堆里面的这个被其他的给松弛了,那就出堆,没用了
if(q.empty()) break;//这个加上比较快
int u=q.top().second;
dis[u]=q.top().first;//只有出堆的时候才确定数值
q.pop();
for(int i=head[u];i>0;i=nex[i]){
if(dis[u]+val[i]<dis[to[i]]){
dis[to[i]]=dis[u]+val[i];
q.push(pair<int ,int>(dis[to[i]],to[i]) );
}
}
}
//printf("%d",dis[4]);
}
其实也可以加一个vis数组去更好的理解,但其实没有用,这里加上一个例题
https://www.luogu.com.cn/problem/P1144
最短路计数,用bfs也可以,但要是边权不在是1,那就只能用dij了,首先先去想一想dij的过程:找点。松弛。入堆。。。。,那么我们在什么地方会有机会判断是否以及被又来过一次呢,那就是在松弛的时候,我们知道假如说dis[g]+val[i]=dis[to[i]]那么这个点是一定之前来过,并且这个dis一定是已经确定的最短路,这时候只要我们发现这个点又来了一边最短路,那么根据加法原理(先不考虑重边)cnt[to[i]]=cnt[g]+cnt[to[i]](cnt是积累最短路数的数组),那假如说这个点将要被松弛,这个点的最短路到达次数是不是就是到达这个点的最短路个数,即cut[to[i]=cnt[g],这就是这道题的一个朴素的想法,至于自环其实和dij没啥关系,也不会去拿自己松弛自己;还有重边,其实也没关系,就是再被到达一次嘛,这里就可以理解为用了乘法原理,但其实加法原理也可以想的通
代码如下
#include<bits/stdc++.h>
using namespace std;
#define pp 2000005
#define ppp 1000005
#define mmm 100003
int head[pp],to[pp],nex[pp],val[pp];
int num=0;
void add(int x,int y,int z){
to[++num]=y;
nex[num]=head[x];
head[x]=num;
val[num]=z;
}
int n,m;
void get_input(){
scanf("%d%d",&n,&m);
int a,b;
for(int i=1;i<=m;i++){
scanf("%d%d",&a,&b);
add(a,b,1);
add(b,a,1);
}
}
int dis[ppp];
long long cnt[ppp];
bool vis[ppp];
priority_queue<pair<int,int> ,vector<pair<int,int> >,greater<pair<int,int> > >q;
void dij(int x){
for(int i=1;i<=n;i++) dis[i]=pp;
dis[x]=0;
vis[x]=true;
q.push(pair<int,int>(dis[x],x));
cnt[1]=1;
while(!q.empty()){
while(!q.empty()&&q.top().first>dis[q.top().second]) q.pop();
if(q.empty())return;
int g=q.top().second;
dis[g]=q.top().first;
vis[g]=true;
q.pop();
for(int i=head[g];i>0;i=nex[i]){
if(dis[g]+val[i]==dis[to[i]]){
cnt[to[i]]+=cnt[g];
cnt[to[i]]%=mmm;
}
if(!vis[to[i]]&&dis[g]+val[i]<dis[to[i]]){
dis[to[i]]=dis[g]+val[i];
cnt[to[i]]=cnt[g];
q.push(pair<int,int>(dis[to[i]],to[i]));
}
}
}
}
void get_ans(){
for(int i=1;i<=n;i++){
printf("%lld\n",cnt[i]);
}
}
int main(){
get_input();
dij(1);
get_ans();
return 0;
}
杂题
一P1629 邮递员送信
这是一个最短路裸,但是我们发现邮递员取得时候是1到搜索所有点的最小距离,但是回来时不一样了,是多个点到1的最小距离,于是我们想起了多元最短路,但是发现时间绝对会炸,所以考虑一下
去的时候是1到多,回来时可不可以也可以1到多呢
可以
只要我们那原图将一个反向图,就可以达到由n + 1到所有点的距离就是我们想要的结果, 也就是add(a,b,c); add(b + n,a + n,c),这样一建立是不是就是两次dij的事情了。