擴點最短路
不把實際位置看作圖上的點,而是把實際位置和該位置的所有狀態的組合看作是圖上的點,BFS 或者 Dijkstra 的過程不變,只是增加了一些點。
864. 獲取所有鑰匙的最短路徑
#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
class Solution {
public:
int rows, columns;
// 最多 6 把鑰匙
int max_k = 6;
int key = 0;
vector<int> move{-1, 0, 1, 0, -1};
bool isCoordinateLegal(int row, int column) {
return row >= 0 && row < rows && column >= 0 && column < columns;
}
// BFS
int shortestPathAllKeys(vector<string> &grid) {
rows = grid.size();
columns = grid[0].size();
// (x, y, 持有鑰匙的狀態)
queue<vector<int>> q;
// 找起點
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < columns; ++j) {
if (grid[i][j] == '@') {
q.emplace(vector<int>{i, j, 0});
} else if (grid[i][j] >= 'a' && grid[i][j] <= 'f') {
// 計算獲得所有鑰匙的最終狀態
key |= (1 << (grid[i][j] - 'a'));
}
}
}
// 表示當前位置的某個狀態是否從佇列中彈出過,狀態是指持有鑰匙的情況
vector<vector<vector<bool>>> visited(rows, vector<vector<bool>>(columns));
for (int i = 0; i < rows; ++i)
for (int j = 0; j < columns; ++j)
visited[i][j].resize(key, false);
int step = 1;
while (!q.empty()) {
int size = q.size();
// 逐層彈出
for (int i = 0; i < size; ++i) {
auto f = q.front();
q.pop();
int x = f[0];
int y = f[1];
// 經過(x, y)後,到達下個點前持有鑰匙的狀態
int s = f[2];
// 四周
for (int j = 0; j < 4; ++j) {
int nx = x + move[j];
int ny = y + move[j + 1];
// 越界
if (!isCoordinateLegal(nx, ny)) continue;
char ch = grid[nx][ny];
// 牆
if (ch == '#') continue;
// 鎖,且沒對應鑰匙
if (ch >= 'A' && ch <= 'F' && (s & (1 << (ch - 'A'))) == 0) continue;
// 鑰匙,更新持有的鑰匙狀態
int ns = s;
if (ch >= 'a' && ch <= 'f')
ns |= (1 << (ch - 'a'));
// 獲得所有鑰匙了
if (ns == key) return step;
if (visited[nx][ny][ns]) continue;
visited[nx][ny][ns] = true;
q.emplace(vector<int>{nx, ny, ns});
}
}
step++;
}
return -1;
}
};
LCP 35. 電動車遊城市
#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
class Solution {
public:
struct cmp {
bool operator()(vector<int> &v1, vector<int> &v2) {
return v1[2] > v2[2];
}
};
int electricCarPlan(vector<vector<int>> &paths, int cnt, int start, int end, vector<int> &charge) {
int n = charge.size();
vector<vector<pair<int, int>>> graph(n);
// 無向圖
for (const auto &item: paths) {
graph[item[0]].emplace_back(make_pair(item[1], item[2]));
graph[item[1]].emplace_back(make_pair(item[0], item[2]));
}
// (點, 到這個點的剩餘電量, 代價)
priority_queue<vector<int>, vector<vector<int>>, cmp> heap;
// vector[i][j] 為到達 i 點時剩餘 j 電量的最小代價
vector<vector<int>> distance(n, vector<int>(cnt + 1, INT_MAX));
// 訪問標記
vector<vector<bool>> visited(n, vector<bool>(cnt + 1, false));
heap.emplace(vector<int>{start, 0, 0});
distance[start][0] = 0;
while (!heap.empty()) {
auto top = heap.top();
heap.pop();
int position = top[0];
int power = top[1];
int cost = top[2];
// 結束
if (position == end) return cost;
if (visited[position][power]) continue;
visited[position][power] = true;
// 充一格電,到圖中擴出來的點,也就是這個點的其他狀態
if (power < cnt
&& !visited[position][power + 1]
&& (cost + charge[position] < distance[position][power + 1])) {
distance[position][power + 1] = cost + charge[position];
heap.emplace(vector<int>{position, power + 1, distance[position][power + 1]});
}
// 不充電,直接去別的點
for (const auto &item: graph[position]) {
int nextPosition = item.first;
int restPower = power - item.second;
int nextCost = cost + item.second;
// 到不了下個點,或者下個狀態已經彈出過,就跳過
if (restPower < 0 || visited[nextPosition][restPower]) continue;
if (nextCost < distance[nextPosition][restPower]) {
distance[nextPosition][restPower] = nextCost;
heap.emplace(vector<int>{nextPosition, restPower, nextCost});
}
}
}
return -1;
}
};
P4568 [JLOI2011] 飛行路線
#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
int n, m, k, s, t;
vector<int> head;
vector<int> nxt;
vector<int> to;
vector<int> weight;
int cnt;
void build() {
head.resize(n, 0);
fill(head.begin(), head.end(), 0);
nxt.resize((m << 1) + 1);
to.resize((m << 1) + 1);
weight.resize((m << 1) + 1);
cnt = 1;
}
void addEdge(int u, int v, int w) {
nxt[cnt] = head[u];
to[cnt] = v;
weight[cnt] = w;
head[u] = cnt;
cnt++;
}
struct cmp {
bool operator()(vector<int> &v1, vector<int> &v2) {
return v1[2] > v2[2];
}
};
int main() {
cin >> n >> m >> k >> s >> t;
build();
for (int i = 0, u, v, w; i < m; ++i) {
cin >> u >> v >> w;
addEdge(u, v, w);
addEdge(v, u, w);
}
// distance[i][j] 為到達 i 點,剩餘免費乘坐次數 j 次的最少代價
vector<vector<int>> distance(n, vector<int>(k + 1, 0x7fffffff));
// 訪問標記
vector<vector<bool>> visited(n, vector<bool>(k + 1, false));
// (點,到點後剩餘的免費次數,最少代價)
priority_queue<vector<int>, vector<vector<int>>, cmp> heap;
distance[s][k] = 0;
heap.emplace(vector<int>{s, k, 0});
while (!heap.empty()) {
auto top = heap.top();
heap.pop();
int u = top[0];
int free = top[1];
int cost = top[2];
// 結束
if (u == t) {
cout << cost;
return 0;
}
if (visited[u][free]) continue;
visited[u][free] = true;
for (int ei = head[u]; ei > 0; ei = nxt[ei]) {
int v = to[ei];
int w = weight[ei];
// 使用一張票
if (free > 0
&& !visited[v][free - 1]
&& cost < distance[v][free - 1]) {
distance[v][free - 1] = cost;
heap.emplace(vector<int>{v, free - 1, cost});
}
// 不使用
if (!visited[v][free]
&& cost + w < distance[v][free]) {
distance[v][free] = cost + w;
heap.emplace(vector<int>{v, free, cost + w});
}
}
}
}