Wormholes POJ - 3259(bellman_ford解读 || spfa优化)

教学楼里有很多教室,这些教室由双向走廊连接。另外,还存在一些单向的秘密通道,通过它们可以回到过去。现在有 N (1 ≤ N ≤ 500) 个教室,编号 1..NM (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;
}

发布了123 篇原创文章 · 获赞 83 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/tsam123/article/details/89930535
今日推荐