分层图最短路
Contents
定义
分层图最短路一般用于解决一种特殊的最短路问题:
给定一个图,在图上可以进行 $k$ 次决策(一般 $k \leq 10$),每次决策并不影响图的结构,只会影响目前的状态/代价。
一般可以将决策前的状态 和 决策后的状态 连接一条边,权值为决策代价。
总体来说,套路如下:
- 给定 $k$ 次决策,将图复制成 $k+1$ 份,分别表示在进行了第 $i$ 次决策后的状态,每一份复制是一层图。
- 从第 $i$ 层,连单向边到第 $i+1$ 层。(有的时候并不需要专门连边,可以在跑最短路的时候顺便转移状态)
- 跑最短路。
例题
例1 洛谷P4568 [JLOI2011]飞行路线
题意
给定 $n$ 个节点,$m$ 条边的无向连通图,每个边具有一个权值。
现给出一个整数 $k$,代表我们可以免费走过最多 $k$ 条边。
给出起点 $s$ 和终点 $t$,求 $s$ 到 $t$ 的最短路?
其中,$2 \leq n \leq 10^4, 1 \leq m \leq 5 \times 10^4, 0 \leq k \leq 10$
题解
分层图模版题。
我们建立 $k+1$ 层图,当我们位于 第 $j$ 层的 $i$ 节点时,代表我们此时走到了节点 $i$,已经用掉了 $j$ 次免费机会。
建图时:(实际上并不需要建图,跑dijkstra的时候顺便转移即可)
- 同一层的权值就和原图一样。
- 从 第 $i$ 层,连单向边到第 $i+1$ 层的权值为 $0$。
然后跑一下Dijkstra就可以了。
Dijkstra 有以下几个注意的点:
priority_queue<node> pq;
,直接定义node
的operator<
函数即可,但是要注意一些细节,如下:struct node { int u, k, d; // 1. 注意,两个const都需要 // 2. pq默认是大顶,所以要反过来用 > bool operator<(const node& other) const { return d > other.d; } }; priority_queue<node> pq;
- Dijkstra使用了一个
int dis[maxn][11]
数组,这是需要在push()
时更新的,而不是pop()
时才更新。在push()
之前先看一下dis
是否比当前的小,如果小,才push()
。这大大减少了pq
内的元素数量。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e4+5;
const int maxm = 5e4+5;
int dis[maxn][11], head[maxn], ecnt = 1;
bool vis[maxn][11];
struct Edge {
int to, nxt, w;
} edges[maxm<<1];
void addEdge(int u, int v, int w) {
Edge e = {v, head[u], w};
edges[ecnt] = e;
head[u] = ecnt++;
}
struct node {
int u, k, d;
// 注意,两个const都需要
// pq默认是大顶,所以要反过来用 >
bool operator<(const node& other) const {
return d > other.d;
}
};
priority_queue<node> pq;
int n,m,K,s,t;
void dijkstra() {
pq.push({s, 0, 0});
while (!pq.empty()) {
node cur = pq.top();
pq.pop();
int u = cur.u, k = cur.k, d = cur.d;
if (vis[u][k]) continue;
vis[u][k] = 1;
for (int e = head[u]; ~e; e = edges[e].nxt) {
int to = edges[e].to, w = edges[e].w;
if (dis[to][k] > d + w) { // 同一层
dis[to][k] = d + w;
pq.push({to, k, d + w});
}
if (k+1 <= K && dis[to][k+1] > d) { // 下一层
dis[to][k+1] = d;
pq.push({to, k+1, d});
}
}
}
}
int main() {
fastio;
cin >> n >> m >> K;
cin >> s >> t;
fill(head, head+maxn, -1);
memset(dis, 63, sizeof(dis));
while (m--) {
int u,v,w; cin >> u >> v >> w;
addEdge(u,v,w);
addEdge(v,u,w);
}
dijkstra();
int ans = 1e9;
for (int k = 0; k <= K; k++) ans = min(ans, dis[t][k]);
cout << ans << endl;
}