SPFA算法
难点: 1. 起始点有自环, 不管正环或者负环, 都需要去判断是VOID还是UNBOUND;
2. 判断负权环是否存在于路径上;(这个算法我一开始想把SPFA某个点进队列次数大于2*n或者更大的数把所有在负环中的点全部找出来,但是有可能做不到,比如从s开始分出2个叉A和B,t在A叉上,A叉上在距离s非常远处有个负环,而B叉上在距离s很近处就有个负环,这样A叉上的负环有可能检查不出来,所以我参考网上的一个算法,感觉没有漏洞,就是从t遍历逆图访问到的点做标记,然后在SPFA时不需要扩展未做标记的点,因为此点不可能到达t。如果有什么不周全的地方,还恳请路过的大牛赐教)
第二种想法代码如下:
#include<cstdio> #include<cstring> #include<queue> #define inf 100000000 using namespace std; int n,m,s,t; int head[1200],cnt,head1[1200]; bool vis[1200],sig[1200],ok[1200]; int dis[1200],mon[1200],times[1200]; struct EDGE{ int v,f,d,next; }edge[22000]; void addedge(int u,int v,int f,int d){ if(head[u]!=-1 && edge[head[u]].f<f) return; if(head[u]!=-1 && edge[head[u]].f>f) head[u]=-1; edge[cnt].v=v; edge[cnt].d=d; edge[cnt].f=f; edge[cnt].next=head[u]; head[u]=cnt++; } int SPFA(int u){ int i,j; memset(vis,0,sizeof(vis)); memset(times,0,sizeof(times)); for(i=0;i<n;i++){ dis[i]=mon[i]=inf; } queue<int>que; que.push(u); vis[u]=1; dis[u]=mon[u]=0; times[u]++; while(!que.empty()){ int tem=que.front(); que.pop(); vis[tem]=0; //把这句移到这里可以把负的自环判断出来 if(times[tem]>n) return 1; for(i=head[tem];i!=-1;i=edge[i].next){ int v=edge[i].v; if(!ok[v]) continue; if(mon[v]>mon[tem]+edge[i].f || (mon[v] == mon[tem]+edge[i].f && dis[v]>dis[tem]+edge[i].d)){ mon[v]=mon[tem]+edge[i].f; dis[v]=dis[tem]+edge[i].d; if(!vis[v]){ que.push(v); vis[v]=1; times[v]++; } } } } return 0; } void dfs(int a){ int i,yes=0; ok[a]=1; for(i=head1[a];i!=-1;i=edge[i].next){ if(!ok[edge[i].v]) dfs(edge[i].v); } } int main(){ int i,j; char str[1000]; while(scanf("%d %d %d %d",&n,&m,&s,&t)==4){ memset(head,-1,sizeof(head)); cnt=0; for(i=1;i<=m;i++){ int u,v,f1,l,f2; char tem; scanf("%s",str); sscanf(str,"(%d,%d,%d[%d]%d)",&u,&v,&f1,&l,&f2); addedge(u,v,f1,l); addedge(v,u,f2,l); } memset(ok,0,sizeof(ok)); memset(head1,-1,sizeof(head1)); for(i=0;i<n;i++){ //建立反图 for(j=head[i];j!=-1;j=edge[j].next){ edge[cnt].v=i; edge[cnt].next=head1[edge[j].v]; head1[edge[j].v]=cnt++; } } dfs(t); //搜索所有能够到达t的点并标记 int kk=SPFA(s); if(mon[t]==inf) printf("VOID\n"); else if(kk==0) printf("%d %d\n",mon[t],dis[t]); else printf("UNBOUND\n"); } return 0; }
第一种想法代码如下:
#include<cstdio> #include<cstring> #include<queue> #define inf 100000000 using namespace std; int n,m,s,t; int head[1200],cnt; bool vis[1200],sig[1200]; int dis[1200],mon[1200],times[1200]; struct EDGE{ int v,f,d,next; }edge[11000]; void addedge(int u,int v,int f,int d){ if(head[u]!=-1 && edge[head[u]].f<f) return; if(head[u]!=-1 && edge[head[u]].f>f) head[u]=-1; edge[cnt].v=v; edge[cnt].d=d; edge[cnt].f=f; edge[cnt].next=head[u]; head[u]=cnt++; } int SPFA(int u){ int i,j; memset(vis,0,sizeof(vis)); memset(times,0,sizeof(times)); for(i=0;i<=n;i++){ dis[i]=mon[i]=inf; } queue<int>que; que.push(u); vis[u]=1; dis[u]=mon[u]=0; times[u]++; while(!que.empty()){ int tem=que.front(); que.pop(); vis[tem]=0; //把这句移到这里可以把负的自环判断出来 if(times[tem]>n*2) //这里当一个点出现n*2次时,可以把所以环上的点标记好 return 1; for(i=head[tem];i!=-1;i=edge[i].next){ int v=edge[i].v; if(mon[v]>mon[tem]+edge[i].f || (mon[v] == mon[tem]+edge[i].f && dis[v]>dis[tem]+edge[i].d)){ mon[v]=mon[tem]+edge[i].f; dis[v]=dis[tem]+edge[i].d; if(!vis[v]){ que.push(v); vis[v]=1; times[v]++; } } } } return 0; } bool dfs(int a){ int i; sig[a]=1; if(a==t) return 1; for(i=head[a];i!=-1;i=edge[i].next){ if(!sig[edge[i].v]){ dfs(edge[i].v); } } return 0; } int main(){ int i,j; char str[1000]; while(scanf("%d %d %d %d",&n,&m,&s,&t)==4){ memset(head,-1,sizeof(head)); cnt=0; for(i=1;i<=m;i++){ int u,v,f1,l,f2; char tem; scanf("%s",str); sscanf(str,"(%d,%d,%d[%d]%d)",&u,&v,&f1,&l,&f2); addedge(u,v,f1,l); addedge(v,u,f2,l); } int kk=SPFA(s); if(mon[t]==inf) printf("VOID\n"); else if(kk==0) printf("%d %d\n",mon[t],dis[t]); else{ bool flag=0; memset(sig,0,sizeof(sig)); for(i=0;i<n;i++){ if(times[i]>n){ if(!sig[i]){ flag=dfs(i); if(sig[t]) break; } } } if(sig[t]) printf("UNBOUND\n"); else printf("%d %d\n",mon[t],dis[t]); } } return 0; }