spfa及其优化
发现spfa居然也有优化,十分的震惊,现在由我细细道来(#^.^#)
Description
给你一个有向且边权全部非负的图,输出1到n的最短路。
Input
第一行两个自然数n(n<=100000)和m(m<=200000),表示点数和边数。接下来m行,每行3个数a,b,l,其中1<=a,b<=n,l<=1000。
Output
仅一个整数,为1到n的最短路。如果无解,输出-1。
Sample Input
10 10
1 5 46
1 10 50
2 7 23
3 4 40
3 7 3
3 9 21
4 9 27
5 8 45
5 10 30
7 10 15
Sample Output
50
Hint
如果是练SPFA,过90分就可以了(不过如果你的SPFA可以过完,在下拜你为师……)。
这个出题人要拜我为师o(* ̄︶ ̄*)o
我们会想一下bell——ford如何进化到spfa.
SPFA对Bellman-Ford算法优化的关键之处在于意识到:只有那些在前一遍松弛中改变了距离估计值的点,才可能引起他们的邻接点的距离估计值的改变。因此,算法大致流程是用一个队列来进行维护,即用一个先进先出的队列来存放被成功松弛的顶点。初始时,源点s入队。当队列不为空时,取出队首顶点, 对它的邻接点进行松弛。如果某个邻接点松弛成功,且该邻接点不在队列中,则将其入队。经过有限次的松弛操作后,队列将为空,算法结束。SPFA算法的实现,需要用到一个先进先出的队列queue 和一个指示顶点是否在队列中的标记数组mark。为了方便查找某个顶点的邻接点,图采用邻接表存储。
这样,我们的加入队列的顺序是随机的,现在,让我们看看两个优化。
一个叫做SLF,一个叫做LLL,先%一下
SLF又称Small Label First 策略. (比较常用)
比较当前点和队首元素,如果小于队首,则插入队首,否则加入队尾
这个优化有什么道理呢,我想是这样的
1.每次加入了边后,都要对可以到达的点松弛,如果先加入队列的元素足够小,那么他的更新效果更加彻底,下一次碰到与这个点相连的点,这个点就很大概率不会入队了,这样就可以减少运算了
嘿嘿(#^.^#)
而LLL又称LLL: Large Label Last 策略. (不太常用)
设队首元素为i,每次弹出时进行判断,队列中所有dist值的平均值为x,若dist(i)>x则将i插入到队尾,查找下一元素,直到找到某一i使得dist(i)<=x,则将i出对进行松弛操作。
据说一些数据卡LLL可以卡到指数级只不过LLL还是挺好的(以后还是LLL少用)
发一个SLF优化的代码
#include<bits/stdc++.h> using namespace std; const int maxn=100000+10; const int INF=0x7FFFFFFF; int pre[maxn]; int dis[maxn]; int path[maxn]; bool vis[maxn]; int head[maxn]; int n,m; int tot,cnt; struct node { int v,w,next; }E[2*maxn]; void add(int u,int v,int w) { E[tot].v=v; E[tot].w=w; E[tot].next=head[u]; head[u]=tot++; } void init() { tot=0; memset(vis,false,sizeof((vis))); memset(head,-1,sizeof(head)); } void spfa(int st) { for(int i=1;i<=n;i++) vis[i]=false,dis[i]=INF; int now,next; dis[st]=0; vis[st]=true; deque<int>q; q.push_back(st); pre[st]=-1; while(!q.empty()) { now=q.front(); q.pop_front(); vis[now]=false; for(int i=head[now];i!=-1;i=E[i].next) { next=E[i].v; if(dis[next]>dis[now]+E[i].w) { dis[next]=dis[now]+E[i].w; pre[next]=now; if(!vis[next]) { vis[next]=true ; if( q.empty()||dis[next]>dis[q.front()]) q.push_back(next); else q.push_front(next); } } } } } void print(int x) { if(pre[x]==-1) return; print(pre[x]); printf("%d ",x); } int main() { init(); scanf("%d%d",&n,&m); // int st,en; // scanf("%d%d",&st,&en); int u,v,w; for(int i=1;i<=m;i++){scanf("%d%d%d",&u,&v,&w);add(u,v,w);} spfa(1); if(dis[n]==INF) {printf("-1");return 0;} printf("%d\n",dis[n]); //printf("%d ",st); //print(en); printf("\n"); return 0; }
不知道为什么加了两个优化以后又T了,莫名其妙(我以后不用LLL了)╭(╯^╰)╮;
#include<bits/stdc++.h> using namespace std; const int maxn=100000+10; const int INF=0x7FFFFFFF; int pre[maxn]; int dis[maxn]; int path[maxn]; bool vis[maxn]; int head[maxn]; int n,m; int tot,cnt; struct node { int v,w,next; }E[2*maxn]; void add(int u,int v,int w) { E[tot].v=v; E[tot].w=w; E[tot].next=head[u]; head[u]=tot++; } void init() { tot=0; memset(vis,false,sizeof((vis))); memset(head,-1,sizeof(head)); } void spfa(int st) { for(int i=1;i<=n;i++) vis[i]=false,dis[i]=INF; int now,next; dis[st]=0; vis[st]=true; deque<int>q; q.push_back(st); pre[st]=-1;int tot=1; int sum=0; while(!q.empty()) { now=q.front(); q.pop_front(); vis[now]=false; tot--; sum-=dis[now] ; for(int i = head[now];i != -1;i = E[i].next) { next = E[i].v; if(dis[next]>dis[now]+E[i].w) { dis[next]=dis[now]+E[i].w; pre[next]=now; if(!vis[next]) { vis[next]=true ; if( q.empty()||dis[next]>dis[q.front()]||dis[next] * tot <= sum) q.push_back(next); else q.push_front(next); tot++;sum+=dis[next]; } } } } } void print(int x) { if(pre[x]==-1) return; print(pre[x]); printf("%d ",x); } int main() { init(); scanf("%d%d",&n,&m); // int st,en; // scanf("%d%d",&st,&en); int u,v,w; for(int i=1;i<=m;i++){scanf("%d%d%d",&u,&v,&w);add(u,v,w);} spfa(1); if(dis[n]==INF) {printf("-1");return 0;} printf("%d\n",dis[n]); //printf("%d ",st); //print(en); printf("\n"); return 0; }