洛谷P2850 [USACO06DEC]蟲洞Wormholes [SPFA求負環,超級源點]

鏈接:P2850

題目描述
While exploring his many farms, Farmer John has discovered a number of amazing wormholes. A wormhole is very peculiar because it is a one-way path that delivers you to its destination at a time that is BEFORE you entered the wormhole! Each of FJ’s farms comprises N (1 ≤ N ≤ 500) fields conveniently numbered 1…N, M (1 ≤ M ≤ 2500) paths, and W (1 ≤ W ≤ 200) wormholes.

As FJ is an avid time-traveling fan, he wants to do the following: start at some field, travel through some paths and wormholes, and return to the starting field a time before his initial departure. Perhaps he will be able to meet himself 😃 .

To help FJ find out whether this is possible or not, he will supply you with complete maps to F (1 ≤ F ≤ 5) of his farms. No paths will take longer than 10,000 seconds to travel and no wormhole can bring FJ back in time by more than 10,000 seconds.

John在他的農場中閒逛時發現了許多蟲洞。蟲洞可以看作一條十分奇特的有向邊,並可以使你返回到過去的一個時刻(相對你進入蟲洞之前)。John的每個農場有M條小路(無向邊)連接着N (從1…N標號)塊地,並有W個蟲洞。其中1<=N<=500,1<=M<=2500,1<=W<=200。 現在John想借助這些蟲洞來回到過去(出發時刻之前),請你告訴他能辦到嗎。 John將向你提供F(1<=F<=5)個農場的地圖。沒有小路會耗費你超過10000秒的時間,當然也沒有蟲洞回幫你回到超過10000秒以前。

輸入格式
Line 1: A single integer, F. F farm descriptions follow.

Line 1 of each farm: Three space-separated integers respectively: N, M, and W

Lines 2…M+1 of each farm: Three space-separated numbers (S, E, T) that describe, respectively: a bidirectional path between S and E that requires T seconds to traverse. Two fields might be connected by more than one path.

Lines M+2…M+W+1 of each farm: Three space-separated numbers (S, E, T) that describe, respectively: A one way path from S to E that also moves the traveler back T seconds.

輸出格式
Lines 1…F: For each farm, output “YES” if FJ can achieve his goal, otherwise output “NO” (do not include the quotes).

輸入輸出樣例
輸入 #1
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
輸出 #1
NO
YES


如果我們把每一條路權值看成通過所用的時間的話,那麼我們便可以把蟲洞的時間看成負權邊,這樣的話如果從一個點出發回到這個點所花的時間小於0,則說明蟲洞起了作用回到了出發時間之前
對於這個理解,我們可以看成負權環,如果我們枚舉每一個點進行尋找是否存在負權環的話可能這道題能過,但是如果數據範圍更大一點可能就會TLE了。
這個時候我們就可以採用超級源點的方法進行解決:
詳情請看:超級源點和超級匯點的建立

<1>同時有多個源點和多個匯點,建立超級源點和超級匯點
<2>同時有多個源點和一個匯點,建立超級源點
<3>同時有多個匯點和一個源點,建立超級匯點

那麼我們這裏是要從多個地方進行尋找是否存在負變權,如果我們建立了超級源點,就只需要從超級源點開始進行找負權環,這樣就可以自動找到是否存在負權環,省去了每一個點枚舉一次找負權環的冗餘操作,也就是<2>的情況。
對於超級源點,我們可以設置爲0也可以設置爲n+1,再將其與所有的圖中的點相連,權值設爲0(因爲超級源點是一個模擬點,爲了方便省時間,所以不存在,不可對整個圖產生影響,故權值設爲0)

接下來就是尋找負權環的操作,對於負權環,我們可以使用SPFA算法進行實現,因爲如果出現負權邊,對於SPFA的思想,他一定會一直圍繞這條邊不斷進行鬆弛,則我們可以通過這個特性來判斷負權環,而這個操作可以通過dfs的SPFA和bfs的SPFA進行。
如果對於dfs的SPFA,則就是在SPFA的鬆弛操作裏,
如果新擴展的這個點之前已經擴展過了,就說明一條路中存在了兩次這個點
那麼就說明出現了環的情況。
部分代碼如下

void pd(int x)
{
	if (flag)
	return ;
	b[x]=1;
	int k=h[x];
	while (k!=-1)
	{
		if (dis[a[k].to]>dis[x]+a[k].dis)
		{
			dis[a[k].to]=dis[x]+a[k].dis;
			if (b[a[k].to])
			{
				flag=1;  //標記出現負權環
				return ;
			}
			else
			pd(a[k].to);
		}
		k=a[k].next;
		}
	b[x]=0;
}

對於bfs的SPFA,我們可以利用這麼一個思想:
如果對於一個有N個點的圖,找到兩個點存在最短路,那麼他們經過的點一定小於等於n個點,那麼如果我們到了當前這個點發現經過的點大於n個了,那麼就說明一定存在環的情況了

部分代碼如下:

while (!q.empty())
		{
			int x=q.front();
			q.pop();
			b[x]=0;
			int k=h[x];
			while (k!=-1)
			{
				if (dis[a[k].to]>dis[x]+a[k].dis)
				{
				    dis[a[k].to]=dis[x]+a[k].dis;
				    cnt[a[k].to]=cnt[x]+1;
				    if (cnt[a[k].to]>n)
				    {
						flag=1;  //標記出現負權環
						return ;
						}
					if(b[a[k].to]==0)
					{
						b[a[k].to]=1;
						q.push(a[k].to);
						} 
					} 
					k=a[k].next;
				}
			}

本題代碼如下:

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
struct CZP
{
	int next,to,dis;
}a[1000001];
int n,m,h[100001],b[100001],flag,t,top,dis[100001],w;
void cun(int from,int to,int dis)
{
	a[++top].next=h[from];
	a[top].to=to;
	a[top].dis=dis;
	h[from]=top;
}
void pd(int x)
{
	if (flag)
	return ;
	b[x]=1;
	int k=h[x];
	while (k!=-1)
	{
		if (dis[a[k].to]>dis[x]+a[k].dis)
		{
			dis[a[k].to]=dis[x]+a[k].dis;
			if (b[a[k].to])
			{
				flag=1;
				return ;
			}
			else
			pd(a[k].to);
		}
		k=a[k].next;
		}
	b[x]=0;
}   //這裏使用了dfs的求負權環操作
int main()
{
	scanf("%d",&t);
	for (int k=1;k<=t;k++)
	{
		flag=0;
	    scanf("%d%d%d",&n,&m,&w);
		for (int i=0;i<=n;i++)
		h[i]=-1;
		for (int i=1;i<=m;i++)
		{
			int x,y,z;
			scanf("%d%d%d",&x,&y,&z);
			cun(x,y,z);
			cun(y,x,z);
			}     //小路爲雙向邊!
		for (int i=1;i<=w;i++)
		{
			int x,y,z;
			scanf("%d%d%d",&x,&y,&z);
			cun(x,y,-z);
		}   //蟲洞改爲負權值
		memset(b,0,sizeof(b));
		for (int i=1;i<=n;i++)
		cun(0,i,0);  //建立超級源點0
		for (int i=1;i<=n;i++)
		dis[i]=100000000;
		dis[0]=0;
		b[0]=1;
		pd(0);  
		if (flag==1)
		printf("YES\n");
		else
		printf("NO\n");
	}
	return 0;
}
*/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章