bzoj 1715: [Usaco2006 Dec]Wormholes 虫洞

1715: [Usaco2006 Dec]Wormholes 虫洞

Time Limit: 5 Sec   Memory Limit: 64 MB
Submit: 910   Solved: 479
[ Submit][ Status][ Discuss]

Description

John在他的农场中闲逛时发现了许多虫洞。虫洞可以看作一条十分奇特的有向边,并可以使你返回到过去的一个时刻(相对你进入虫洞之前)。John的每个农场有M条小路(无向边)连接着N (从1..N标号)块地,并有W个虫洞。其中1<=N<=500,1<=M<=2500,1<=W<=200。 现在John想借助这些虫洞来回到过去(出发时刻之前),请你告诉他能办到吗。 John将向你提供F(1<=F<=5)个农场的地图。没有小路会耗费你超过10000秒的时间,当然也没有虫洞回帮你回到超过10000秒以前。

Input

* Line 1: 一个整数 F, 表示农场个数。

* Line 1 of each farm: 三个整数 N, M, W。

* Lines 2..M+1 of each farm: 三个数(S, E, T)。表示在标号为S的地与标号为E的地中间有一条用时T秒的小路。

* Lines M+2..M+W+1 of each farm: 三个数(S, E, T)。表示在标号为S的地与标号为E的地中间有一条可以使John到达T秒前的虫洞。

Output

* Lines 1..F: 如果John能在这个农场实现他的目标,输出"YES",否则输出"NO"。

Sample Input

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

Sample Output

NO
YES

HINT

Source


在机房激起了一波讨论后

又自己看了很多帖子、博客

一种方法是跑spfa

判某个点入队大于n次

为什么是入队大于n次而不是松弛大于n次呢

我们先举个反例

可以假设一个有两个节点的图

从1-2有10条重边

边权分别从10到1

然后完全有可能遍历的时候从边权为10的边开始更新10次

但是可以不存在负权回路

而如果真的有负环的话,它是会无限更新的

所以这样可以避免误判,并且一定找得到负环

但是spfa的复杂度是O(kE)的(k是平均每个点入队的次数)(据说一般k==2)

所以如果入队n次

可以想象这是有多慢的

所以有时可以直接判入队次数大于某个特定的数

比如一两百

听说一般不会错

但我不敢用QAQ


还有一种方法就是dfs了

如果从一个点开始dfs出去的点能回来

即路径中多次出现某个点

那么就是负环了

我们将dis初始化为inf

然后枚举每个点

将它的dis设为0

然后去dfs


然而我们会发现

我们并不需要去求最短路

我们只需要求出负环

而照上面那样做

也不是求最短路

最后每个点的dis都会变为0

所以我们直接把dis初始化为0

这样只有出现负权边,才会去走

就避免了求最短路的时间

实测快了20倍左右


刚开始邻接表开的刚刚好RE了

乱开了一波10w才AQAQ

#include<cstdio>
#include<cstring>
const int N=505,M=2507;
struct node
{
	int to,next,c;
}e[100000];
int cnt;
int first[N];
void insert(int u,int v,int c)
{
	e[++cnt]=(node){v,first[u],c};first[u]=cnt;
	e[++cnt]=(node){u,first[v],c};first[v]=cnt;
}
int dis[N],visit[N];
bool flag;
void dfs(int x)
{
	visit[x]=1;
	for(int k=first[x];k;k=e[k].next)
	{
		if(dis[e[k].to]>dis[x]+e[k].c)
		{
			if(visit[e[k].to])
			{
				flag=1;return;
			}
			dis[e[k].to]=dis[x]+e[k].c;
			dfs(e[k].to);
		}
	}
	visit[x]=0;
}
int main()
{
	int tt;
	scanf("%d",&tt);
	while(tt--)
	{
		int n,m,w;
		scanf("%d %d %d",&n,&m,&w);
		memset(dis,0,sizeof(dis));
		memset(first,0,sizeof(first));
		int u,v,c;
		for(int i=1;i<=m;i++)
		{
			scanf("%d %d %d",&u,&v,&c);
			insert(u,v,c);
		}
		for(int i=1;i<=w;i++)
		{
			scanf("%d %d %d",&u,&v,&c);
			e[++cnt]=(node){v,first[u],-c};first[u]=cnt;
		}
		flag=0;
		memset(visit,0,sizeof(visit));
		for(int i=1;i<=n&&!flag;i++)	dfs(i);
		if(flag)	printf("YES\n");
		else printf("NO\n");
	}
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值