题目链接:https://vjudge.net/problem/HDU-2544
题意:给n个点,m条边,求点1到点n的最短路。
思路:
今天学了下bellman_ford,抄抄模板。dijkstra算法和该算法都是单源最短路径算法,但是dij不能适用含负权边的图。而bellman-ford算法适用于负权边,原理是进行n-1次松弛操作,每次都要对m条边进行松弛,所以算法复杂度是O(mn),比dijkstra要高。如果n-1次操作之后还能进行松弛,说明存在负环。
AC code:
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int maxn=105; const int maxm=10005; const int inf=0x3f3f3f3f; int n,m,dis[maxn],cnt,loop; struct node{ int u,v,w; }edge[maxm<<1]; void adde(int u,int v,int w){ edge[++cnt].v=v; edge[cnt].u=u; edge[cnt].w=w; } void bellman_ford(int s){ for(int i=1;i<=n;++i) dis[i]=inf; dis[s]=0; //n-1次松弛 for(int i=1;i<n;++i){ bool ok=0; for(int j=1;j<=cnt;++j){ int u=edge[j].u,v=edge[j].v,w=edge[j].w; if(dis[v]>dis[u]+w){ dis[v]=dis[u]+w; ok=1; } } if(!ok) break; } //loop=1说明存在负环 for(int i=1;i<=cnt;++i){ int u=edge[i].u,v=edge[i].v,w=edge[i].w; if(dis[v]>dis[u]+w){ loop=1; break; } } } int main(){ while(scanf("%d%d",&n,&m),n||m){ cnt=0,loop=0; for(int i=1;i<=m;++i){ int u,v,w; scanf("%d%d%d",&u,&v,&w); adde(u,v,w); adde(v,u,w); } bellman_ford(1); printf("%d ",dis[n]); } return 0; }
思路2:
spfa是bellman-ford的队列优化版本,使用条件一样,vis标记该点是否在队列中,被更新的结点如果不在队列中要重新入队。也可以用来求是否存在负环,用updcnt数组记录每个结点更新次数,如果==n说明更新了n次,即存在负环。
然后说一下spfa的时间复杂度是O(km),k为常数,但最坏情况是O(mn),比较毒瘤,慎用!
下面的代码是luoguP3385,判负环。
AC code:
#include<cstdio> #include<algorithm> #include<cstring> #include<queue> using namespace std; const int maxn=2005; const int maxm=6005; const int inf=0x3f3f3f3f; int T,n,m,head[maxn],vis[maxn],cnt,upd[maxn],dis[maxn]; struct node{ int v,w,nex; }edge[maxm]; void adde(int u,int v,int w){ edge[++cnt].v=v; edge[cnt].w=w; edge[cnt].nex=head[u]; head[u]=cnt; } bool spfa(){ for(int i=1;i<=n;++i) vis[i]=0,upd[i]=0,dis[i]=inf; queue<int> que; que.push(1); vis[1]=1,dis[1]=0,++upd[1]; while(!que.empty()){ int u=que.front();que.pop(); vis[u]=0; for(int i=head[u];i;i=edge[i].nex){ int v=edge[i].v,w=edge[i].w; if(dis[v]>dis[u]+w){ dis[v]=dis[u]+w; if(!vis[v]){ ++upd[v]; if(upd[v]>n) return true; vis[v]=1; que.push(v); } } } } return false; } int main(){ scanf("%d",&T); while(T--){ scanf("%d%d",&n,&m); for(int i=1;i<=n;++i) head[i]=0; cnt=0; for(int i=1;i<=m;++i){ int u,v,w; scanf("%d%d%d",&u,&v,&w); if(w<0){ adde(u,v,w); } else{ adde(u,v,w); adde(v,u,w); } } if(spfa()) printf("YE5 "); else printf("N0 "); } return 0; }
拓展:
bellman-ford算法还能用来求最长路或者是判断正环,只用改下松弛条件即可。改变dis数组的定义为从源点到其它结点最长路径的长度,初始化为0,松弛时,如果dis[v]<dis[u]+w,则更新,其它操作一样。入poj1860。
AC code:
#include<cstdio> #include<cstring> #include<algorithm> #include<queue> using namespace std; const int maxn=205; int n,m,s,head[maxn],vis[maxn],upd[maxn],cnt; double V,dis[maxn]; struct node{ int v,nex; double r,c; }edge[maxn<<1]; void adde(int u,int v,double r,double c){ edge[++cnt].v=v; edge[cnt].r=r; edge[cnt].c=c; edge[cnt].nex=head[u]; head[u]=cnt; } bool spfa(int s,double V){ for(int i=1;i<=n;++i) dis[i]=0,vis[i]=0,upd[i]=0; dis[s]=V,vis[s]=1; queue<int> que; que.push(s); ++upd[s]; while(!que.empty()){ int u=que.front();que.pop(); vis[u]=0; for(int i=head[u];i;i=edge[i].nex){ int v=edge[i].v; double r=edge[i].r,c=edge[i].c; if(dis[v]<(dis[u]-c)*r){ dis[v]=(dis[u]-c)*r; if(!vis[v]){ ++upd[v]; if(upd[v]>n) return true; que.push(v); vis[v]=1; } } } } if(dis[s]>V) return true; return false; } int main(){ scanf("%d%d%d%lf",&n,&m,&s,&V); for(int i=1;i<=n;++i) head[i]=0; cnt=0; for(int i=1;i<=m;++i){ int u,v; double ruv,cuv,rvu,cvu; scanf("%d%d%lf%lf%lf%lf",&u,&v,&ruv,&cuv,&rvu,&cvu); adde(u,v,ruv,cuv); adde(v,u,rvu,cvu); } if(spfa(s,V)) printf("YES "); else printf("NO "); return 0; }