給定 n 個節點、m 條無向邊,每個節點屬於某一層。相鄰層間移動代價為 c。求從節點 1 到節點 n 的最小代價。
關鍵在於如何高效處理「層間移動代價 c」。若直接連接所有層間節點,邊數會爆炸。解法是引入虛擬層節點(匯點):
此建圖方式將層間轉移簡化為經過虛擬節點,邊數從 O(N²) 降為 O(N + M)。
作者嘗試過 `addEdge(i, d+N, 0)` 但 WA。正確做法是虛擬節點 → 節點(代價 0),即 `addEdge(d+N, i, 0)`。原因在於:
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;
typedef pair<int,int> pii;
const int N = 100000;
const int INF = 0x3f3f3f3f;
int n, m, c;
int dist[N*3+10], vis[N*3+10];
int nindex, head[N*3+10];
struct Edge { int to, val, next; } edge[N*20];
inline void addEdge(int u, int v, int val) {
edge[nindex] = {v, val, head[u]};
head[u] = nindex++;
}
void init() {
nindex = 0;
memset(head, -1, sizeof(head));
memset(vis, 0, sizeof(vis));
scanf("%d%d%d", &n, &m, &c);
for (int i = 1; i <= n; i++) {
int layer; scanf("%d", &layer);
vis[layer] = 1;
// 虛擬節點 → 實體節點(代價0)
addEdge(layer + N, i, 0);
// 實體節點 → 相鄰層虛擬節點(代價c)
if (layer > 1) addEdge(i, layer + N - 1, c);
if (layer < n) addEdge(i, layer + N + 1, c);
}
for (int i = 0; i < m; i++) {
int u, v, w; scanf("%d%d%d", &u, &v, &w);
addEdge(u, v, w);
addEdge(v, u, w);
}
// 相鄰層虛擬節點互連
for (int i = 2; i <= n; i++) {
if (vis[i] && vis[i-1]) {
addEdge(i+N, i-1+N, c);
addEdge(i-1+N, i+N, c);
}
}
}
int dijkstra() {
memset(dist, INF, sizeof(dist));
priority_queue<pii, vector<pii>, greater<pii>> pq;
dist[1] = 0;
pq.push({0, 1});
while (!pq.empty()) {
auto [d, u] = pq.top(); pq.pop();
if (d != dist[u]) continue;
for (int e = head[u]; e != -1; e = edge[e].next) {
int v = edge[e].to, w = edge[e].val;
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
pq.push({dist[v], v});
}
}
}
return dist[n] == INF ? -1 : dist[n];
}
int main() {
int T; scanf("%d", &T);
for (int cas = 1; cas <= T; cas++) {
init();
printf("Case #%d: %d\n", cas, dijkstra());
}
return 0;
}
此題展示了「虛擬節點建圖」處理層狀結構轉移的經典技巧,關鍵在於正確建立單向邊以保持語義一致性。