LightOJ - 1074 Extended Traffic(SPFA + DFS 求出负环)

题意
多组数据
给出街道的数量
每个街道的点权
u v 权值等于街道的点权差相减的三次方(线段的终点减起点)

要求求出1到个个点的距离如果无法到达就输出?

因为边权是一个差的3次方,则有可能是负数。

所有有可能有负环, 负环上的点就不存在最短路径,所有找出负环,其他的点SPFA求最短路,如果连通就输出,不连通就归为无法到达的一类。如果在负环也是无法到达输出?。

用SPFA判断负环,如果一个点入队超过n次的话就dfs它,标记它所包含的连同分量。
此题不仅需要判断负环,还要求出不在负环上的点的最短路,应为在负环上的点是没有最短路的,那么我们只要标记出所有负环上的点,不对他们求最短路,这样spfa跑出来的就是不在环上点的最短路了

DFS+SPFA 利用系统栈,改变优化的顺序,如果有点优化了先前存在系统栈中的点,就判断有负环,并且此点在负环上

void SPFA(int s) {
	if(cyclic[s]) return ;	
	vis[s] = true;
 	for(int i = h[s]; i; i = e[i].nes) {
 		if(cyclic[s]) return ;
		int v = e[i].to;
		int dis = e[i].w;
		if(!cyclic[v]&&d[v] > d[s] + dis) {
			d[v] = d[s] + dis;
			if(!vis[v]) SPFA(v);
			else dfs(v);
		}
	}
	vis[s] = false;
}

BFS+SPFA 如果点入队次数超过n次 判断有负环,并且此点在环上

void SPFA(int s) {
	queue<int> q;
	memset(inq, 0, sizeof(inq));
	memset(d, 0x3f, sizeof(d));
	memset(nums, 0, sizeof(nums));
	d[s] = 0;
	q.push(s);
	inq[s] = true;
	nums[s]++;
	while(!q.empty()) {
		int u = q.front(); q.pop();
		inq[u] = false;
		if(cyclic[u]) continue; //此点在环上跳过
		for(int i = h[u]; i; i = e[i].nes) {
			int v = e[i].to;
			int dis = e[i].w;
			if(!cyclic[v]  && d[v] > d[u] + dis) { //只优化不在环上的点
				d[v] = d[u] + dis;
				if(!inq[v]) {
					q.push(v);
					inq[v] = true;
					if(++nums[v] > n) {
						dfs(v); // 标记连同分量
					}
				}
			}
		}
	}
}

dfs dfs标记函数

void dfs(int s) { //标记环
	cyclic[s] = true;
	for(int i = h[s]; i; i = e[i].nes) {
		if(!cyclic[e[i].to]) dfs(e[i].to);
	}
}

AC码

#include <cstring>
#include<cstdio>
#include<iostream>
#include<queue>
#include<unordered_map> 
#define ll long long
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 1e5 + 10;
int d[maxn];
//bool vis[maxn];
int test1, n;
ll busy[maxn];
int h[maxn],p;
bool cyclic[maxn]; //用来记录点是否在环上
bool vis[maxn];
bool inq[maxn];
int nums[maxn];

struct edge {
	ll nes, to, w;
}e[100000];

inline void add(int u, int v, int l) {
	e[++p].nes = h[u];
	e[p].to = v;
	e[p].w = l;
	h[u] = p;
}

void dfs(int s) {
	cyclic[s] = true;
	for(int i = h[s]; i; i = e[i].nes) {
		if(!cyclic[e[i].to]) dfs(e[i].to);
	}
}
//
//void SPFA(int s) {
//	queue<int> q;
//	memset(inq, 0, sizeof(inq));
//	memset(d, 0x3f, sizeof(d));
//	memset(nums, 0, sizeof(nums));
//	d[s] = 0;
//	q.push(s);
//	inq[s] = true;
//	nums[s]++;
//	while(!q.empty()) {
//		int u = q.front(); q.pop();
//		inq[u] = false;
//		if(cyclic[u]) continue;
//		for(int i = h[u]; i; i = e[i].nes) {
//			int v = e[i].to;
//			int dis = e[i].w;
//			if(!cyclic[v] 
//			&& d[v] > d[u] + dis) {
//				d[v] = d[u] + dis;
//				if(!inq[v]) {
//					q.push(v);
//					inq[v] = true;
//					if(++nums[v] > n) {
//						dfs(v);
//					}
//				}
//			}
//		}
//	}
//}

void SPFA(int s) {
	if(cyclic[s]) return ;	
	vis[s] = true;
 	for(int i = h[s]; i; i = e[i].nes) {
 		if(cyclic[s]) return ;
		int v = e[i].to;
		int dis = e[i].w;
		if(!cyclic[v]&&d[v] > d[s] + dis) {
			d[v] = d[s] + dis;
			if(!vis[v]) SPFA(v);
			else dfs(v);
		}
	}
	vis[s] = false;
}


inline ll bolck(ll u, ll v) {
	ll ans = 1;
	for(int i = 0; i < 3; i++) {
		ans *= (busy[v] - busy[u]);
	}
	return ans;
} 


int main() {
	scanf("%d", &test1);
	int _case = 0;
	while(test1--) {
		memset(cyclic, false,sizeof(cyclic));
		memset(h, 0, sizeof(h));
		p = 0;
		//memset(e, 0, sizeof(e));
		int m, query;
		scanf("%d", &n);
		for(int i = 1; i <= n; i++) {
			scanf("%lld", &busy[i]);
		}
		scanf("%d", &m);
		for(int i = 1; i <= m; i++) {
			ll u, v, l;
			scanf("%d%d", &u, &v);
			l = bolck(u, v);
			add(u, v, l);
		}
		scanf("%d", &query);
		memset(vis, 0, sizeof(vis));
		memset(d, 0x3f, sizeof(d));
		d[1] = 0;
		SPFA(1);
		printf("Case %d:\n", ++_case);
		while(query--) {
			int w;
			scanf("%d", &w);
			if(cyclic[w] || d[w] == inf || d[w] < 3) printf("?\n");
			else printf("%d\n", d[w]); 
		}
	}
	return 0;	
} 

dfs 判断负环

inline bool dfs_spfa(int u)//dfs版的spfa(其实与bfs的差不多,只是改了搜索顺序)
{
	vis[u]=1;
	for(int i=head[u],v;i;i=edge[i].nxt)
	{
		v=edge[i].to;
		if(dis[v]>dis[u]+edge[i].w)
		{
			dis[v]=dis[u]+edge[i].w;
			if(vis[v] || dfs_spfa(v)) return 1;//如果访问过了(即找到负环)或者已经存在了负环
		}
	}
	vis[u]=0;//注意改回来
	return 0;//没找到
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值