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;
}