目录
算法1Dijkstra暴力求解
算法2 A*算法
题目链接
https://www.acwing.com/problem/content/description/180/
题目很直接,就是求第K短路
算法1
Dijkstra可以求解第K短路:
Dijkstra算法是基于贪心的,回顾一下堆优化的Dijkstra,从堆中第一次取到的目标点的dist值是最小值,因为我们之前只求最短路,因此还会用一个数组标记哪些访问哪些没访问,就是因为从堆中第一次取出来的一定是最短路。如果把最短路取出来以后,再次从堆中取出了一个已经被取过一次的点,那么这个节点记录的就是次短路的信息(除去最短路的最短路)。因此可以用Dijkstra求解第k短路。
这里就不需要写,,哪些都是为了求解最短路而设定的条件,现在直接将边加入即可。同时用一个数组记录每个点从堆中取出的次数,以此来判断节点从堆中取出的时候是第几次取出,对应着这是第几短路。
但是这个算法,太过于暴力,会多遍历好多点,一般情况下会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*算法。
定义一个函数,表示后续的代价估计值,假设有另一个函数表示的是点后续的真实代价,要求:
如果,我们可以根据估计代价从小到大排序,这样的话,按照这个顺序走下去,直接可以得到最短路。因为当前的估计代价就是真实代价,也就是说,你现在在起点s,终点是t,你知道了从s到t的真实代价的所有值,并为它排好了序,取出最小的当然就是从s到t 的最短路了。也就是说越接近 那么算法的执行效率越高,但是一定要满足。如果过分的小了,就比如,那么该算法就退化为堆优化的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*其实就是比堆优化多了一个启发式函数,当然,也可以将堆优化看成是启发式函数的情况,堆中的序列按照距离+边长+启发函数(后继的代价)之和排序而已。
结束语:为什么要放入犯傻的记录中呢?其实这个题我TLE+WA了一下午,原因是建立反边的时候有一个表头没有初始化,然后就是中间有一个点的记号写错了,查错半天QAQ