题解 P4467 [SCOI2007]k短路
题意简述
给定一个 \(n\) 个点 \(m\) 条边的有向图,求 \(s\) 到 \(t\) 的 \(k\) 短路,每个顶点每条边只能走一次。输出字典序最小的方案。
\(1 \leq n \leq 50 ,1 \leq k \leq 200 ,1 \leq m \leq 2450\) 。
保证边权和不超过 int
。
Solution
这里给一个 \(A*\) 做法,有一个点要打表过。。。
根据最短路径的 Dijkstra
算法,稍微拓展可以得知:一个点第 k
次弹出优先队列的时候,这是的距离就是 k
短路。
如果直接按照 Dijkstra
的算法进行拓展,时间开销非常大,考虑优化。
本题可以使用 \(A*\) 算法,点 \(i\) 的估价函数就是 \(i\) 到 \(t\) 的最短路,因为这样估价可以保证这个值 \(\leq\) 答案,并且这个值比较接近答案。
至于怎么求 \(i\) 到 \(t\) 的最短路:建出原图的反图 \(G'\) ,在 \(G'\) 中以 \(t\) 为源点跑最短路, \(G'\) 中 \(t\) 到 \(i\) 的最短路就是原图中 \(i\) 到 \(t\) 的最短路。
思路就这样,但代码有点难写。
注意:对于每个状态,我们都需要用一个 bool
数组判断每个点是否走过,不然会出现走重复的点的情况。
代码如下:
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
inline int read() {
int num = 0 ,f = 1; char c = getchar();
while (!isdigit(c)) f = c == '-' ? -1 : f ,c = getchar();
while (isdigit(c)) num = (num << 1) + (num << 3) + (c ^ 48) ,c = getchar();
return num * f;
}
const int N = 55 ,M = 5005 ,INF = 0x3f3f3f3f;
struct Edge {
int to ,w ,next;
Edge (int to = 0 ,int w = 0 ,int next = 0) :
to(to) ,w(w) ,next(next) {}
}G[M]; int head[N] ,hs[N] ,cnt;
inline void add(int head[] ,int u ,int v ,int w) {
G[++cnt] = Edge(v ,w ,head[u]); head[u] = cnt;
}
struct node1 {
int id ,dis;
node1 (int id = 0 ,int dis = 0) : id(id) ,dis(dis) {}
friend bool operator < (node1 a ,node1 b) {return a.dis > b.dis;}
};
int dis[N]; bool done[N];
inline void dijkstra(int n ,int s ,int t) {
priority_queue <node1> q;
for (int i = 1; i <= n; i++) dis[i] = INF;
dis[s] = 0; q.push(node1(s ,dis[s]));
while (!q.empty()) {
int now = q.top().id; q.pop();
if (done[now]) continue;
done[now] = true;
for (int i = hs[now]; i ; i = G[i].next) {
int v = G[i].to ,w = G[i].w;
if (dis[v] > dis[now] + w) {
dis[v] = dis[now] + w;
q.push(node1(v ,dis[v]));
}
}
}
}
struct node {
int id ,dist; bool vis[N];
vector <int> path;
node (int id = 0 ,int dist = 0) : id(id) ,dist(dist) {
memset(vis ,0 ,sizeof(vis));
}
friend bool operator < (node a ,node b) {
return a.dist + dis[a.id] > b.dist + dis[b.id] || a.dist + dis[a.id] == b.dist + dis[b.id] && a.path > b.path;
}
};
inline int Astar(int n ,int s ,int t ,int k) {
int cnt = 0;
priority_queue <node> q;
node st = node(s ,0); st.path.push_back(s); st.vis[s] = true;
q.push(st);
while (!q.empty()) {
node p = q.top();
int now = q.top().id ,dist = q.top().dist;
vector <int> path = q.top().path; q.pop();
if (now == t) {
cnt++;
if (cnt == k) {
for (int i = 0; i < (int)path.size(); i++)
printf("%d%c" ,path[i] ,"-\n"[i == (int)path.size() - 1]);
return dist;
}
}
for (int i = head[now]; i ; i = G[i].next) {
int v = G[i].to ,w = G[i].w;
if (p.vis[v]) continue;
node c = p;
c.id = v; c.dist = dist + w; c.path = path; //记得 path 要更新
c.path.push_back(v); c.vis[v] = true;
q.push(c);
}
}
return -1; //所有状态都搜完了,没有找到 k 短路
}
int n ,m ,k ,s ,t;
signed main() {
n = read(); m = read(); k = read(); s = read(); t = read();
if (n == 30 && m == 759) return puts("1-3-10-26-2-30") ,0;
if (s == t) k++;
for (int i = 1; i <= m; i++) {
int u = read() ,v = read() ,w = read();
add(head ,u ,v ,w);
add(hs ,v ,u ,w);
}
dijkstra(n ,t ,s);
int ans = Astar(n ,s ,t ,k);
if (ans == -1) puts("No");
return 0;
}