POJ-3255 Roadblocks(次短路)

题意:给出一张无向图,求出从源点到终点的严格次短路(<最短路)长度。

思路:可以有三种求解方法。

一. Dijkstra算法做适当的修改。即维护最短路和次短路两个数组,次短路和最短路更新都需要入队,因为最终的次短路可能是源点到点u的次短路+点u到终点的最短路,也可能是源点到点u的最短路+点u到终点的次短路。复杂度大约是O(2*nlogn)


Code:

#include <string.h>
#include <cstdio>
#include <queue>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 5005;
const int maxm = 100005;

struct node1
{
	int v, w, next;
} edge[maxm*2];
struct node
{
	int v, w, key;
	node(int a, int b, int c): v(a), w(b), key(c){}
	bool operator<(const node k)const
	{
		return w > k.w;
	}
};
priority_queue<node> q;
int n, m, no;
int head[maxn], dis[maxn][2], vis[maxn][2];
inline void init()
{
	no = 0;
	memset(head, -1, sizeof head);
	memset(dis, 0x3f, sizeof dis);
	memset(vis, 0, sizeof vis);
	while(!q.empty()) q.pop();
}
inline void add(int u, int v, int w)
{
	edge[no].v = v, edge[no].w = w;
	edge[no].next = head[u]; head[u] = no++;
}
void DJ()
{
	dis[1][0] = 0;
	q.push(node(1, 0, 0));
	while(!q.empty())
	{
		node top = q.top(); q.pop();
		if(vis[top.v][top.key]) continue;
		vis[top.v][top.key] = 1;
		int k = head[top.v];
		while(k != -1)
		{
			int tmp = top.w + edge[k].w;
			if(dis[edge[k].v][0] > tmp)
			{
				if(dis[edge[k].v][0] != inf) 
				{
					dis[edge[k].v][1] = dis[edge[k].v][0];
					q.push(node(edge[k].v, dis[edge[k].v][1], 1));
				}
				dis[edge[k].v][0] = tmp;
				q.push(node(edge[k].v, dis[edge[k].v][0], 0));
			}
			else if(dis[edge[k].v][1] > tmp && dis[edge[k].v][0] != tmp) 
			//第二个判断存在为了求严格次短路(<最短路),不存在则为了求非严格次短路(<=最短路) 
			{
				dis[edge[k].v][1] = tmp;
				q.push(node(edge[k].v, dis[edge[k].v][1], 1));
			}
			k = edge[k].next;
		}
	}
}
int main()
{
	int u, v, w;
	scanf("%d %d", &n, &m); init();
	for(int i = 1; i <= m; ++i)
	{
		scanf("%d %d %d", &u, &v, &w);
		add(u, v, w);
		add(v, u, w);
	}
	DJ();
	printf("%d\n", dis[n][1]);	//打印次短路 
	return 0;
}

二. 先求出源点到终点的最短路以及终点到源点的最短路并存在两个数组中,然后枚举所有边,进行判断。因为次短路一定存在于dis[u][0]+E[u, v]+dis[v][1]或者dis[v][0]+E[u, v]+dis[u][1]中(假设当前边为E[u, v], dis[i][0]表示源点到点i的最短路,dis[i][1]表示终点到点i的最短路)。这是网上的说法,其实我分析了一下,因为这儿是无向图,上述两个式子其中一个是起到了走当前边一次的效果,另一个起到了走当前边两次的效果。所以这样做是把走次短路的所有可能走法都给枚举出来了,因为次短路形成要么是必走当前边,加上两端点到源点终点的最短路径,要么是最短路径中的边折返再走一次形成次短路。另外网上说可以扩展成求第K短路,个人感觉 不可行,我认为这个方法仅适用于次短路中,因为比如第3短路,它可能形成的方式会有最短路径上的一条边折返三次形成。(以上均为个人见解).

复杂度是O(2*NM+M)

Code:

#include <algorithm>
#include <string.h>
#include <cstdio>
#include <queue>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 5005;
const int maxm = 100005;

struct node1
{
	int v, w, next;
} edge[maxm*2];
queue<int> q;
int n, m, no;
int head[maxn], dis[maxn][2], vis[maxn];
inline void init()
{
	no = 0;
	memset(head, -1, sizeof head);
	memset(dis, 0x3f, sizeof dis);
}
inline void add(int u, int v, int w)
{
	edge[no].v = v, edge[no].w = w;
	edge[no].next = head[u]; head[u] = no++;
}
void SPFA(int s, int id)
{
	while(!q.empty()) q.pop();
	memset(vis, 0, sizeof vis);
	dis[s][id] = 0, vis[s] = 1;
	q.push(s);
	while(!q.empty())
	{
		int top = q.front(); q.pop();
		vis[top] = 0;
		int k = head[top];
		while(k != -1)
		{
			if(dis[edge[k].v][id] > dis[top][id]+edge[k].w)
			{
				dis[edge[k].v][id] = dis[top][id]+edge[k].w;
				if(!vis[edge[k].v])
				{
					vis[edge[k].v] = 1;
					q.push(edge[k].v);
				}
			}
			k = edge[k].next;
		}
	}
}
int main()
{
	int u, v, w, ans, mins;
	scanf("%d %d", &n, &m); init();
	for(int i = 1; i <= m; ++i)
	{
		scanf("%d %d %d", &u, &v, &w);
		add(u, v, w);
		add(v, u, w);
	}
	SPFA(1, 0); SPFA(n, 1);
	ans = inf, mins = dis[n][0];
	for(int i = 1; i <= n; ++i)
	{
		int tmp, k = head[i];
		while(k != -1)
		{
			tmp = dis[i][0]+dis[edge[k].v][1];
			printf("%d\n", tmp);
			if(tmp+edge[k].w > mins) ans = min(ans, tmp+edge[k].w);
			tmp = dis[i][1]+dis[edge[k].v][0];
			printf("%d\n", tmp);
			if(tmp+edge[k].w > mins) ans = min(ans, tmp+edge[k].w);
			k = edge[k].next;
		}
	}
	printf("%d\n", ans);
	return 0;
}

三. A*+SPFA求第K短路。以后除了次短路求第K短路都用这个吧,感觉其它有风险(貌似没有其它了...),但是对于一些数据这种方法复杂度可能会偏高。代码中有小细节解释。

复杂度网上说是:O((n+m)logn + mlogm + klogk)。


Code:

#include <algorithm>
#include <string.h>
#include <cstdio>
#include <queue>
using namespace std;
const int inf = 0x3f3f3f3f;
const int maxn = 5005;
const int maxm = 100005;

struct node1
{
	int v, w, next;
} edge[maxm*2];
struct node
{
	int v, g, h;
	node(int a, int b, int c):v(a), g(b), h(c){}
	bool operator<(const node k)const
	{
		return h+g > k.h+k.g;
	}
};
queue<int> q;
priority_queue<node> Q; 
int n, m, no, mins;
int head[maxn], dis[maxn], vis[maxn], cnt[maxn];
inline void init()
{
	no = 0;
	memset(head, -1, sizeof head);
	memset(dis, 0x3f, sizeof dis);
	memset(cnt, 0, sizeof cnt);
}
inline void add(int u, int v, int w)
{
	edge[no].v = v, edge[no].w = w;
	edge[no].next = head[u]; head[u] = no++;
}
void SPFA()
{
	while(!q.empty()) q.pop();
	memset(vis, 0, sizeof vis);
	dis[n] = 0, vis[n] = 1;
	q.push(n);
	while(!q.empty())
	{
		int top = q.front(); q.pop();
		vis[top] = 0;
		int k = head[top];
		while(k != -1)
		{
			if(dis[edge[k].v] > dis[top]+edge[k].w)
			{
				dis[edge[k].v] = dis[top]+edge[k].w;
				if(!vis[edge[k].v])
				{
					vis[edge[k].v] = 1;
					q.push(edge[k].v);
				}
			}
			k = edge[k].next;
		}
	}
}
int A_Star()
{
	while(!Q.empty()) Q.pop();
	Q.push(node(1, 0, dis[1]));
	while(!Q.empty())
	{
		node top = Q.top(); Q.pop();
		++cnt[top.v];
		if(top.v == n)	/*求严格次短路应该是这种判断,但存在会T的风险*/
		{
			if(cnt[n] == 1) mins = top.g;
			else if(top.g > mins) return top.g;
		}
		//下面这张判断其实是不对的,它求得是非严格次短路,本题数据竟然可以AC,真的弱 
	/*	if(cnt[top.v] > 2) continue;
		if(cnt[n] == 2) return top.g;*/
		int k = head[top.v];
		while(k != -1)
		{
			Q.push(node(edge[k].v, top.g+edge[k].w, dis[edge[k].v]));
			k = edge[k].next;
		}
	}
	return -1;
}
int main()
{
	int u, v, w;
	scanf("%d %d", &n, &m); init();
	for(int i = 1; i <= m; ++i)
	{
		scanf("%d %d %d", &u, &v, &w);
		add(u, v, w);
		add(v, u, w);
	}
	SPFA();
	int ans = A_Star();
	printf("%d\n", ans);
	return 0;
}

继续加油~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值