AcWing 178 第K短路(第二次重新来过)

目录

题目链接 

算法1Dijkstra暴力求解

算法2 A*算法


题目链接 

https://www.acwing.com/problem/content/description/180/

题目很直接,就是求第K短路

算法1

Dijkstra可以求解第K短路:

Dijkstra算法是基于贪心的,回顾一下堆优化的Dijkstra,从堆中第一次取到的目标点的dist值是最小值,因为我们之前只求最短路,因此还会用一个visit[N]数组标记哪些访问哪些没访问,就是因为从堆中第一次取出来的一定是最短路。如果把最短路取出来以后,再次从堆中取出了一个已经被取过一次的点,那么这个节点记录的就是次短路的信息(除去最短路的最短路)。因此可以用Dijkstra求解第k短路。

这里就不需要写,if (dist[to] > dist[from] + d ),哪些都是为了求解最短路而设定的条件,现在直接将边加入即可。同时用一个数组number[N]记录每个点从堆中取出的次数,以此来判断节点从堆中取出的时候是第几次取出,对应着这是第几短路。

但是这个算法,太过于暴力,会多遍历好多点,一般情况下会TLE(除非数据特别弱)。

TLE代码

/**
 * Author : correct
 */
#include <bits/stdc++.h>
using namespace std;
#define mem(a, b) memset(a, b, sizeof a)
const int N = 1010, M = 100200;
int head[M], nex[M], to[M], ed[M], cnt;
void add(int a, int b, int c){
	++cnt;
	to[cnt] = b;
	ed[cnt] = c;
	nex[cnt] = head[a];
	head[a] = cnt;
}
int number[N];
int dist[N];
int S, T, K, n, m;
struct p{
	int x, dist;
	p(){

	}
	p(int x, int y){
		this->x = x;
		this->dist = y;
	}
	bool operator < (const p& a)const{
		return this->dist < a.dist;
	}
	bool operator > (const p& a)const{
		return this->dist > a.dist;
	}
};
void dijkstra(){
	priority_queue<p, vector<p>, greater<p> > q;
	q.push(p(S, 0));
	while (q.size()){
		p t = q.top();
		q.pop();
                number[t.x]++;// 每次出队,记录这是第几次
		if (t.x == T){
			dist[number[t.x]] = t.dist;
			if (number[T] == K)return;// 如果这是第k次,说明已经是第K短路
		}
		for (int i = head[t.x]; ~i; i = nex[i]){
			int y = to[i];
			int c = ed[i];
			if (number[y] >= K)continue;// 求k短路,其余的点的k+1,k+2等等短路用不到
			q.push(p(y, c + t.dist));
		}
	}
}
int main()
{
	ios::sync_with_stdio(0);
	mem(head, -1);
	mem(to, -1);
	cnt = 0;
	mem(number, 0);
	mem(dist, 0x3f);
	cin >> n >> m;
	while (m--){
		int a, b, c;
		cin >> a >> b >> c;
		add(a, b, c);
	}
	cin >> S >> T >> K;
	if (S == T)K++;// 题目中说每个K短路至少含一条边,对于起点和终点相同的情况
                   //显然应该去掉自身到自身为0的情况,可以通过求K+1短路间接实现
	dijkstra();
	if (dist[K] == 0x3f3f3f3f)dist[K] = -1;
	cout << dist[K];
	return 0;
}

算法2 

上述算法太暴力,超时很正常,需要优化。 可以扔给TLE算法一个启发函数,让他每次选择一个尽可能优的点去扩展。A*算法。

定义一个函数f(x),表示x后续的代价估计值,假设有另一个函数g(x)表示的是x点后续的真实代价,要求:f(x)\leq g(x)

如果f(x)=g(x),我们可以根据估计代价从小到大排序,这样的话,按照这个顺序走下去,直接可以得到最短路。因为当前的估计代价就是真实代价,也就是说,你现在在起点s,终点是t,你知道了从s到t的真实代价的所有值,并为它排好了序,取出最小的当然就是从s到t 的最短路了。也就是说f(x)越接近g(x) 那么算法的执行效率越高,但是一定要满足f(x)\leq g(x)。如果过分的小了,就比如f(x)=0,那么该算法就退化为堆优化的Dijkstra 了,依然可以求解,但是如果过分大了,就会导致这个点被压在堆底无法弹出,可能会产生错误。其余的就与堆优化的Dijkstra一样了。

本题中启发函数的选取,可以建立反边,然后用从T开始的到每个顶点的最短路当作启发函数的值。因为最短路一定满足上述条件,而且非常接近。

输入完毕后,建立反向边,然后堆优化Dijkstra跑一遍以T为源点的最短路,将每个dist值当作启发函数的值。然后跑A*。

AC代码

/**
 * Author : correct
 */
#include <bits/stdc++.h>
using namespace std;
#define mem(a, b) memset(a, b, sizeof a)
const int N = 1010, M = 100200;
int head[N], nex[M], to[M], ed[M], cnt, head2[N];
void add(int* h, int a, int b, int c){
	++cnt;
	to[cnt] = b;
	ed[cnt] = c;
	nex[cnt] = h[a];
	h[a] = cnt;
}
int number[N];
int dist[N];
bool vis[N];
int S, T, K, n, m;
int f(int x){
	return dist[x];
}
struct p{
	int x, dist;
	p(){}
	p(int x, int y){this->x = x;this->dist = y;}
	bool operator < (const p& a)const{return this->dist < a.dist;}
	bool operator > (const p& a)const{return this->dist > a.dist;}
};
struct p1{
	p a;
	int sum;
	p1(){}
	p1(p a, int sum){this->a = a;this->sum = sum;}
	bool operator < (const p1& t)const{return this->sum < t.sum;}
	bool operator > (const p1& t)const{return this->sum > t.sum;}
};
void dijkstra(){
	priority_queue<p, vector<p>, greater<p> > q;
	q.push(p(T, 0));
	dist[T] = 0;
	while (q.size()){
		p t = q.top();
		q.pop();
		if (vis[t.x])continue;
		vis[t.x] = 1;
		for (int i = head2[t.x]; ~i; i = nex[i]){
			int y = to[i];
			int c = ed[i];
			if (dist[y] > dist[t.x] + c){
				dist[y] = dist[t.x] + c;
				q.push(p(y, dist[y]));
			}
		}
	}
}
int A_Star(){
	priority_queue<p1, vector<p1>, greater<p1> > q;
	q.push(p1(p(S, 0), 0));
	while (q.size()){
			p1 t = q.top();
			q.pop();
			if (t.a.x == T){
				number[t.a.x]++;
				if (number[T] == K)return t.a.dist;
			}
			for (int i = head[t.a.x]; ~i; i = nex[i]){
				int y = to[i];
				int c = ed[i];
				if (number[y] >= K)continue;
				q.push(p1(p(y, c + t.a.dist), c + t.a.dist + f(y)));
			}
		}
	return -1;
}
int main()
{
	ios::sync_with_stdio(0);
	mem(head, -1);
	mem(to, -1);
	mem(head2, -1);
	mem(vis, 0);
	cnt = 0;
	mem(number, 0);
	mem(dist, 0x3f);
	cin >> n >> m;
	while (m--){
		int a, b, c;
		cin >> a >> b >> c;
		add(head, a, b, c);
		add(head2, b, a, c);
	}
	cin >> S >> T >> K;
	if (S == T)K++;// 最少包含一条边
	dijkstra();
	cout << A_Star();
	return 0;
}

A*其实就是比堆优化多了一个启发式函数,当然,也可以将堆优化看成是启发式函数f(x)\equiv 0的情况,堆中的序列按照距离+边长+启发函数(后继的代价)之和排序而已。

结束语:为什么要放入犯傻的记录中呢?其实这个题我TLE+WA了一下午,原因是建立反边的时候有一个表头没有初始化,然后就是中间有一个点的记号写错了,查错半天QAQ

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

猜你喜欢

转载自blog.csdn.net/weixin_43701790/article/details/104950662
今日推荐