教学楼里有很多教室,这些教室由双向走廊连接。另外,还存在一些单向的秘密通道,通过它们可以回到过去。现在有 N (1 ≤ N ≤ 500) 个教室,编号 1..N, M (1 ≤ M ≤ 2500) 条走廊,和 W (1 ≤ W ≤ 200) 条秘密通道。
DY在养猫之余,还是一个时间旅行爱好者。她希望从一间教室出发,经过一些走廊和秘密通道,回到她出发之前的某个时间。
共有F (1 ≤ F ≤ 5) 组数据。对每组数据,判断DY是否有回到过去的可能性。不存在耗时超过10,000秒的走廊,且不存在能带DY回到10,000秒之前的秘密通道。
输入格式
首先是一个整数F,表示接下来会有F组数据。
每组数据第1行:分别是三个空格隔开的整数:N,M和W
第2行到M+1行:三个空格分开的数字(S,E,T)描述双向走廊:从S到E需要耗费T秒。两个教室可能由一个以上的路径来连接。
第M +2到M+ W+1行:三个空格分开的数字(S,E,T)描述秘密通道:从S到E可以使时间倒流T秒。
输出格式
F行,每行对应一组数据。 每组数据输出单独的一行,” YES”表示能满足要求,”NO”表示不能满足要求。
输入样例
2
3 3 1
1 2 2
1 3 4
2 3 1
3 1 3
3 2 1
1 2 3
2 3 4
3 1 8
输出样例
NO
YES
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
const int inf=(1<<30);
const int maxn=505;
using namespace std;
struct node
{
int u,v,val;
node(int u1,int v1,int val1):u(u1),v(v1),val(val1){}
};
vector<node>s;
int d[maxn];
int n,m,x;
bool bf()
{
fill(d,d+maxn,inf);
d[1]=0;
for(int i=0;i<n;i++)
{
int len=s.size();
for(int j=0;j<len;j++)
{
if(d[ s[j].v ]>d[ s[j].u ]+s[j].val)
{
d[ s[j].v ]=d[ s[j].u ]+s[j].val;
if(i==n-1)
return true;
}
}
}
return false;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
s.clear();
scanf("%d%d%d",&n,&m,&x);
int a,b,val;
for(int i=0;i<m;i++)
{
scanf("%d%d%d",&a,&b,&val);
s.push_back(node(a,b,val));
s.push_back(node(b,a,val));
}
while(x--)
{
scanf("%d%d%d",&a,&b,&val);
s.push_back(node(a,b,-val));
}
if(bf())
printf("YES\n");
else
printf("NO\n");
}
return 0;
}
使用 贝尔曼福特来做,就是套贝尔曼福特的模板,一次一次的松弛每一条边,如果能一直松弛下去,那就是出现了负环,我们就及时的return 掉,如果没有一直的松弛,我们就可以求出最短路。我们有n个点,我们每个点都需要松弛一次,每一次松弛,我们需要松弛完 所有的边 ,看看有没有还可以松弛的,尽量不留下漏网之鱼 。这个负环是怎么形成的呢 ,为什么说他是能一直松弛的呢?首先我们得知道 bellman_ford 的工作原理,就是对图中的每一条边进行遍历松弛,一直循环n(顶点的个数)次,这样如果能找到松弛边时,就进行松弛操作,注意是对每一个边进行松弛操作(这个是很重要的)。
下面我举一个例子来说明一下这个数据是怎么跑的,我先说一下,首先存图,将一条边的起点和终点都装进一个结构体中,然后使用vector 邻接表存图,这样我们vector中的size() 就是这个图中的边的数量,然后对每一条边都进行判断能不能松弛,(是将所有的边都跑,这个要注意,只要能进行松弛我们就进行松弛)。
下面我就说一下,这个负环是怎么形成的。一条边有两个点,一个是起点一个是终点,看是否d[终点]>d[起点]+val边 ,如果可以的话,我们就需要更新权值,就是这样,如果出现负环的话 ,它就会一直的进行松弛下去,所以我们进行一个判断就可以判断出负环,有的题目可能只跑一次就会出现最终结果(没有负环),我们直接跑n次(点个数),如果这个时候,还能松弛某些个点的时候,这时就会出现负环了,我们就及时的退出。避免TLE造成WA。
跑数据代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
const int inf=(1<<30);
const int maxn=505;
using namespace std;
struct node
{
int u,v,val;
node(int u1,int v1,int val1):u(u1),v(v1),val(val1){}
};
vector<node>s;
int d[maxn];
int n,m,x;
bool bf()
{
int g=0;
int kase=0;
fill(d,d+maxn,inf);
d[1]=0;
while(1)
{
int len=s.size();
for(int j=0;j<len;j++)
{
if(d[ s[j].v ]>d[ s[j].u ]+s[j].val)
{
d[ s[j].v ]=d[ s[j].u ]+s[j].val;
}
}
printf("第%d次\n",++kase);
cout<<"|";
for(int i=1;i<=n;i++)
printf("%5d",i);
cout<<"|"<<endl;
cout<<"|";
for(int i=1;i<=n;i++)
if(d[i]==inf)
printf(" inf");
else
printf("%5d",d[i]);
cout<<"|"<<endl;
cout<<endl;
}
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
s.clear();
scanf("%d%d%d",&n,&m,&x);
int a,b,val;
for(int i=0;i<m;i++)
{
scanf("%d%d%d",&a,&b,&val);
s.push_back(node(a,b,val));
s.push_back(node(b,a,val));
}
while(x--)
{
scanf("%d%d%d",&a,&b,&val);
s.push_back(node(a,b,-val));
}
bf();
}
return 0;
}
/*
10
4 0 4
1 2 -1
2 3 1
3 1 1
1 4 1
*/
判断负环的另一种方法,也是bellman_ford 的优化版,使用了队列,抛弃了双重循环,使复杂度降低了,代码更简单。
先放出大神的博客 spfa +优化
我们使用邻接表方式存图,使用队列,一开始将起始点放进队列中,并且标记该点。队列不为空的条件,从队列中不断取出一个点,然后将这个点的标记去掉(可以重复进队列,这个就是当一圈的松弛完毕后,由于起始点的(自己到自己的距离发生改变),其他的点还可以再进行松弛,再从这个点开始松弛,就是这样一次次的进行),将于这个点相连的点都进行一次松弛判断 ,能松弛,如果没有被标记的话,进队,被标记的话,不进队 。然后再进行循环,出队下一个点,所以到最后,我们如果没有出现负环的话,我们就只进行n(n个点)的大循环,所以我们就可以进行负环判断,来一个计数数组,当一个点松弛了大于n次的时候 ,我们就可以确定出现了负环 ,我们就应该及时的退出,减少多余的运行。
代码:
#include<iostream>
#include<queue>
#include<vector>
#include<cstring>
#include<cstdio>
const int maxn=505;
typedef long long ll;
const int inf=(1<<30);
using namespace std;
struct node
{
int v,c;
node(int v1,int c1):v(v1),c(c1){}
};
vector<node>edge[maxn];
int d[maxn];
int vis[maxn];
int k[maxn];
int n,m,x;
int kase=0;
bool bell(int s)
{
memset(vis,0,sizeof(vis));
memset(k,0,sizeof(k));
fill(d,d+maxn,inf);
d[s]=0;
queue<int>q;
q.push(s); //源点进队
vis[s]=1;
while(!q.empty())
{
printf("%d\n",++kase);
int u=q.front();
q.pop();
vis[u]=0; //消除标记,重新入队
int len=edge[u].size();
for(int i=0;i<len;i++) //在于其相连的所有点
{
int v=edge[u][i].v;
int c=edge[u][i].c;
if(d[v]>d[u]+c) // 三角形性质
{
d[v]=d[u]+c;
if(vis[v]==0)
{
k[v]++;
vis[v]=1;
if(k[v]>=n-1)
{
return true;
}
q.push(v);
}
}
}
}
return false;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
kase=0;
memset(edge,0,sizeof(edge));
scanf("%d%d%d",&n,&m,&x);
int u,v,val;
for(int i=0;i<m;i++)
{
scanf("%d%d%d",&u,&v,&val);
edge[u].push_back(node(v,val));
edge[v].push_back(node(u,val));
}
while(x--)
{
scanf("%d%d%d",&u,&v,&val);
edge[u].push_back(node(v,-val));
}
if(bell(1))
printf("YES\n");
else
printf("NO\n");
}
return 0;
}
。