算是模板题,给定一个有向图,顶点从0到n-1编号,必须从0开始出发,问是否存在负环
一开始写了一个Bellman-Ford,超时,然后就放弃了,写了个spfa的bfs版本,过了,然后又写了一个spfa的dfs版本,wa,然后改了一个下午,还是wa
然后上网找了一下代码,发现很多人写的都是BF算法,看了一下自己的一样,怎么会超时呢??
后来才发现,我读错题意了,我本来是理解为只要图中有负环就好了,所有枚举了所有的顶点作为源点去BF,所以才超时,现在是规定了0作为源点,所以就AC了
然后就把spfa的dfs版本改了一下,不要枚举所有源点,规定0为源点,然后就AC了,再修改了一些细节地方,跑出了最好成绩0.008排名第5
从这个地方可以看出,数据中,有的图是不连通的,所以有的连通分量有负环有的没有,如果枚举了所以的源点的话,有可能会WA
但是有个地方比较困惑,我第一次写spfa的bfs版本的时候也是枚举了所有源点的,可以AC,照理来说应该是WA的,然后改为单源点后也是AC,是数据问题??不可能啊
希望有人指点一下
下面给出代码
Bellman-Ford
#include <cstdio> #include <cstring> #define N 1010 #define M 2010 #define INF 0x3f3f3f3f int d[N],u[M],v[M],w[M]; int n,m; int BF(int s) //Bellman-Ford { int k,i,j; for(i=0; i<n; i++) d[i]=INF; d[s]=0; for(k=1; k<n; k++) //进行V-1次松弛 for(int i=0; i<m; i++) //枚举所有的边 { int x=u[i],y=v[i]; if( d[x]+w[i] < d[y] ) d[y]=d[x]+w[i]; } int OK=1; for(int i=0; i<m; i++) //检查一遍所有的边 { int x=u[i],y=v[i]; if( d[x]+w[i] < d[y] ) //还能进行松弛 { OK=0; break; } } return OK; } int main() { int T; scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); for(int i=0; i<m; i++) scanf("%d%d%d",&u[i],&v[i],&w[i]); if(!BF(0)) //有负环 printf("possible\n"); else printf("not possible\n"); } return 0; }
SPFA_BFS
#include <cstdio> #include <cstring> #include <queue> using namespace std; #define N 1010 #define NN 5 #define M 2010 #define INF 0x3f3f3f3f int n,m; int d[N],f[N],nnext[M],u[M],v[M],w[M]; //用数组来模拟邻接表,并且使用头插法 int c[N]; //记录每个顶点进队的次数 bool vis[N]; //标记顶点在队内 void input() { scanf("%d%d",&n,&m); memset(f,0,sizeof(f)); //memset(nnext,0,sizeof(nnext)); for(int i=1; i<=m; i++) //边集数组从下标1保存到m { scanf("%d%d%d",&u[i],&v[i],&w[i]); nnext[i]=f[u[i]]; //头插法 f[u[i]]=i; //头插法 } /* printf("打印邻接表:\n"); for(int i=0; i<n; i++) { printf("%d:\n",i); int j=f[i]; while(j!=0) { printf("%d %d\n",v[j],w[j]); j=nnext[j]; } } */ return ; } int spfa_bfs(int s) { queue <int> q; memset(d,0x3f,sizeof(d)); d[s]=0; memset(c,0,sizeof(c)); memset(vis,0,sizeof(vis)); q.push(s); vis[s]=1; c[s]=1; //顶点入队vis要做标记,另外要统计顶点的入队次数 int OK=1; while(!q.empty()) { int x; x=q.front(); q.pop(); vis[x]=0; //队头元素出队,并且消除标记 for(int k=f[x]; k!=0; k=nnext[k]) //遍历顶点x的邻接表 { int y=v[k]; if( d[x]+w[k] < d[y]) { d[y]=d[x]+w[k]; //松弛 if(!vis[y]) //顶点y不在队内 { vis[y]=1; //标记 c[y]++; //统计次数 q.push(y); //入队 if(c[y]>NN) //超过入队次数上限,说明有负环 return OK=0; } } } } return OK; } int main() { int T; scanf("%d",&T); while(T--) { input(); if(!spfa_bfs(0)) //有负环 printf("possible\n"); else printf("not possible\n"); } return 0; }
SPFA_DFS
//时间最快0.008 #include <cstdio> #include <cstring> #define N 1010 #define M 2010 #define INF 0x3f3f3f3f int d[N],f[N]; bool vis[N]; struct edge { int u,v,w,next; }e[M]; int n,m; void input() { scanf("%d%d",&n,&m); memset(f,0,sizeof(f)); for(int i=1; i<=m; i++) //读入所有边 { scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w); int u=e[i].u; e[i].next=f[u]; f[u]=i; } /* printf("打印邻接表:\n"); for(int i=0; i<n; i++) { printf("%d:\n",i); for(int k=f[i]; k!=0; k=e[k].next) printf("%d %d\n",e[i].v,e[i].w); } */ return ; } int spfa_dfs(int u) { vis[u]=1; for(int k=f[u]; k!=0; k=e[k].next) { int v=e[k].v,w=e[k].w; if( d[u]+w < d[v] ) { d[v]=d[u]+w; if(!vis[v]) { if(spfa_dfs(v)) return 1; } else return 1; } } vis[u]=0; return 0; } int main() { int T; scanf("%d",&T); while(T--) { input(); for(int i=0; i<n; i++) { vis[i]=0; d[i]=INF;} d[0]=0; if(spfa_dfs(0)) printf("possible\n"); else printf("not possible\n"); } return 0; }
求指教 SPFA_BFS 枚举所有起点,按道理应该是WA的,为什么AC了…………
#include <cstdio> #include <cstring> #include <queue> using namespace std; #define N 1010 #define NN 5 #define M 2010 #define INF 0x3f3f3f3f int n,m; int d[N],f[N],nnext[M],u[M],v[M],w[M]; //用数组来模拟邻接表,并且使用头插法 int c[N]; //记录每个顶点进队的次数 bool vis[N]; //标记顶点在队内 void input() { scanf("%d%d",&n,&m); memset(f,0,sizeof(f)); //memset(nnext,0,sizeof(nnext)); for(int i=1; i<=m; i++) //边集数组从下标1保存到m { scanf("%d%d%d",&u[i],&v[i],&w[i]); nnext[i]=f[u[i]]; //头插法 f[u[i]]=i; //头插法 } /* printf("打印邻接表:\n"); for(int i=0; i<n; i++) { printf("%d:\n",i); int j=f[i]; while(j!=0) { printf("%d %d\n",v[j],w[j]); j=nnext[j]; } } */ return ; } int spfa_bfs(int s) { queue <int> q; memset(d,0x3f,sizeof(d)); d[s]=0; memset(c,0,sizeof(c)); memset(vis,0,sizeof(vis)); q.push(s); vis[s]=1; c[s]=1; //顶点入队vis要做标记,另外要统计顶点的入队次数 int OK=1; while(!q.empty()) { int x; x=q.front(); q.pop(); vis[x]=0; //队头元素出队,并且消除标记 for(int k=f[x]; k!=0; k=nnext[k]) //遍历顶点x的邻接表 { int y=v[k]; if( d[x]+w[k] < d[y]) { d[y]=d[x]+w[k]; //松弛 if(!vis[y]) //顶点y不在队内 { vis[y]=1; //标记 c[y]++; //统计次数 q.push(y); //入队 if(c[y]>NN) //超过入队次数上限,说明有负环 return OK=0; } } } } return OK; } int main() { int T; scanf("%d",&T); while(T--) { input(); int s; for(s=0; s<n; s++) //枚举所有源点 { int tmp=spfa_bfs(s) ; //for(int i=0; i<n; i++) //printf("%d ",d[i]); //printf("\n"); if(!tmp) break; } if(s<n) //有负环 printf("possible\n"); else printf("not possible\n"); } return 0; }