单源最短路的拓展应用
1137. 选择最佳线路 - AcWing题库
最短路、虚拟原点
两种做法
-
建立虚拟远点,假设从点0出发,能到达所有起点,求最短路
-
反向建图,将朋友家作为起点,选取朋友家到所有出发站点的最短距离
方法一:
int e[N], ne[N], h[M], w[N], idx;
bool st[M];
int dist[M], n, m, s;
inline void add(int a, int b, int c) {
e[++idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx;
}
inline int dijkstra() {
Heap(PII) heap;
memset(dist, 0x3f);
fill(st, st + n + 1, 0);
dist[0] = 0;
heap.push({0, 0});
while (heap.size()) {
auto t = heap.top(); heap.pop();
int ver = t.second, d = t.first;
if (st[ver]) continue;
st[ver] = true;
for (int i = h[ver]; i; i = ne[i]) {
int j = e[i];
if (dist[j] > d + w[i]) {
dist[j] = d + w[i];
heap.push({dist[j], j});
}
}
}
return dist[s] == 0x3f3f3f3f ? -1 : dist[s];
}
int main() {
while (~scanf("%d%d%d", &n, &m, &s)) {
idx = 0;
fill(h, h + n + 1, 0);
for (int i = 0; i < m; i++) {
int a, b, c; scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
scanf("%d", &m);
while (m --) {
int x; scanf("%d", &x);
add(0, x, 0);
}
printf("%d\n", dijkstra());
}
return 0;
}
方法二:
int e[N], ne[N], h[M], w[N], idx;
bool st[M];
int dist[M], n, m, s;
inline void add(int a, int b, int c) {
e[++idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx;
}
inline void dijkstra() {
Heap(PII) heap;
memset(dist, 0x3f);
fill(st, st + n + 1, 0);
dist[s] = 0;
heap.push({0, s});
while (heap.size()) {
auto t = heap.top(); heap.pop();
int ver = t.second, d = t.first;
if (st[ver]) continue;
st[ver] = true;
for (int i = h[ver]; i; i = ne[i]) {
int j = e[i];
if (dist[j] > d + w[i]) {
dist[j] = d + w[i];
heap.push({dist[j], j});
}
}
}
}
int main() {
while (~scanf("%d%d%d", &n, &m, &s)) {
idx = 0;
fill(h, h + n + 1, 0);
for (int i = 0; i < m; i++) {
int a, b, c; scanf("%d%d%d", &a, &b, &c);
add(b, a, c);
}
dijkstra();
int res = 0x3f3f3f3f;
scanf("%d", &m);
while (m --) {
int x; scanf("%d", &x);
res = min(res, dist[x]);
}
if (res == 0x3f3f3f3f) res = -1;
printf("%d\n", res);
}
return 0;
}
1131. 拯救大兵瑞恩 - AcWing题库
最短路、分层图、二进制状态压缩
本题的主要关键点在于处理拿到钥匙后不同状态的处理。
在本题中,拿到一个钥匙后会产生不同的状态,不同的状态下的最短路的情况也不同。
所以不同于一般的最短路,我们可以将维护的dist
数组再增加一个维度,来表示不同状态下的最短路的情况。
对于本题,有许多不同的状态,对于拿到不同的钥匙,我们可以用二进制来的某一位来表示,那么dist[i][state]
表示在状态\(state\)下,经过点\(i\)的最短路的情况
/*
* 由于经过每个点的时候,会携带不同的钥匙
* 所以每个点的状态可以表示为f(i, j, state)
*
* 如果遇到墙或者无法开的门时,那么距离不用更新
* 如果遇到可以打开的门(a, b),则更新:
* dist(a, b, state) = [dist(x, y, state | p) + 1]
*
* 如果遇到可以直接走的门,更新:
* dist(a, b, state) = [dist(x, y, state) + 1]
*
* 如果当前的位置上有钥匙,那么更新一下当前点的状态
* 因为当前拿钥匙的状态是从多个点转移过来的
* 要选取其中步数最少的点转移过来
* dist(a, b, state | key) = [dist(x, y, state)]
*
* 为了简化点数表示,可以将每一个坐标赋值
*/
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
#define x first
#define y second
constexpr int N = 11, M = 400, P = 1 << 10;
// 邻接表
int h[N * N], e[M], ne[M], w[M], idx;
// key是每个点拥有的钥匙的状态, id 是每个点对应的编号
// dist代表的是每个点的状态f
int key[N * N], id[N][N], dist[N * N][P];
int n, m, p, k;
// 标记数组,判断某个状态是否搜索过
bool st[N * N][P];
// 标记已经处理过的边
set<PII> edge;
inline void add(int a, int b, int c) {
e[++idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx;
}
inline void build() {
int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1};
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
for (int u = 0; u < 4; u++) {
int x = i + dx[u], y = j + dy[u];
if (x < 1 or x > n or y < 1 or y > m) continue;
int a = id[i][j], b = id[x][y];
if (!edge.count({a, b})) {
add(a, b, 0);
add(b, a, 0);
}
}
}
inline int bfs() {
memset(dist, 0x3f, sizeof dist);
dist[1][0] = 0;
deque<PII> q;
q.emplace_front(make_pair(1, 0));
while (q.size()) {
auto t = q.front();
q.pop_front();
if (st[t.x][t.y]) continue;
st[t.x][t.y] = true;
if (t.x == n * m) return dist[t.x][t.y];
if (key[t.x]) {
int state = t.y | key[t.x];
if (dist[t.x][state] > dist[t.x][t.y]) {
dist[t.x][state] = dist[t.x][t.y];
q.push_front({t.x, state});
}
}
for (int i = h[t.x]; i; i = ne[i]) {
int j = e[i];
if (w[i] and !(t.y >> (w[i] - 1) & 1)) continue;
if (dist[j][t.y] > dist[t.x][t.y] + 1) {
dist[j][t.y] = dist[t.x][t.y] + 1;
q.push_back({j, t.y});
}
}
}
return -1;
}
int main() {
scanf("%d%d%d", &n, &m, &p);
for (int i = 1, cnt = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
id[i][j] = cnt++;
scanf("%d", &k);
while (k --) {
int x1, x2, y1, y2, G;
scanf("%d%d%d%d%d", &x1, &y1, &x2, &y2, &G);
int a = id[x1][y1], b = id[x2][y2];
edge.insert({a, b});
edge.insert({b, a}); // 这个忘记加了
if (G) add(a, b, G), add(b, a, G);
}
build();
scanf("%d", &k);
while (k --) {
int x, y, g;
scanf("%d%d%d", &x, &y, &g);
int c = id[x][y];
key[c] |= 1 << (g - 1);
}
printf("%d\n", bfs());
return 0;
}
1134. 最短路计数 - AcWing题库
最短路、递推
对于计数类的问题,我们都可以通过dp的方式来解决。
本题中,cnt[i]表示到达点i的最短路径的所有方案
边界: f[1] = 1
转移 :
当dist[j] > dist[i] + 1
: f[j] = f[i]
当dist[j] == dist[i] + 1
: f[j] += f[i]
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 1e5 + 100, M = N * 4, mod = 100003;
int h[N], e[M], ne[M], idx;
int dist[N], f[N];
int n, m;
inline void add(int a, int b) {
e[++idx] = b; ne[idx] = h[a]; h[a] = idx;
}
inline void spfa() {
memset(dist, 0x3f, sizeof dist);
dist[1] = 0;
cnt[1] = 1;
queue<int> q;
q.emplace(1);
while (q.size()) {
int t = q.front();
q.pop();
for (int i = h[t]; i; i = ne[i]) {
int j = e[i];
if (dist[j] > dist[t] + 1) {
dist[j] = dist[t] + 1;
cnt[j] = cnt[t];
q.emplace(j);
}
else if (dist[j] == dist[t] + 1) {
cnt[j] = (cnt[j] + cnt[t]) % mod;
}
}
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 0; i < m; i++) {
int a, b; scanf("%d%d", &a, &b);
add(a, b); add(b, a);
}
spfa();
for (int i = 1; i <= n; i++) printf("%d\n", f[i]);
return 0;
}
383. 观光 - AcWing题库
最短路、分层图、递推
本题要求出比最短路长1的路径数目。
如果这种路径存在,那么一定是次短路,那么我们假设这种路径存在,统计次短路的方案数。
不同于一般的最短路只需要维护最短路距离
,本题还需要再格外记录次短路距离
,这样在进行求解的过程中,包含了最短路和次短路两个图的距离,所以我们可以用分层图的方法来求解,对于一种状态看作一种维度,那么多个状态就可以用多个维度记录。在本题中,只需要记录下两个状态的值:最短路距离dist[i][0]
和次短路距离dist[i][1]
。
对于计数问题,我们可以仿照1134. 最短路计数 - AcWing题库,从dp的角度出发去进行统计,令cnt[i][0]
表示到达点i时的最短路的方案数,cnt[i][1]
表示次短路的方案数,那么就有:
-
边界:
cnt[s][0] = 1
,cnt[s][1] = -1
-
最短路更新
dist[i][0] > w
:之前的最短路的状态转移到次短路,更新当前最短路的状态 -
属于最短路的方案
dist[i][0] == w
:统计最短路的方案 -
次短路更新
dist[1][0] > w
:更新次短路的状态 -
属于次短路的方案
dist[i][1] == w
:统计次短路的方案
最后,判断一下次短路是否符合题目的要求,输出答案即可。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
constexpr int N = 1010, M = 10010, INF = 0x3f3f3f3f;
int h[N], e[M], w[M], ne[M], idx;
bool st[N][2];
int dist[N][2], cnt[N][2];
int n, m, s, f;
struct Edge {
int ver, type, dis;
bool operator> (const Edge &W) const {
return dis > W.dis;
}
};
inline void add(int a, int b, int c) {
e[++idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx;
}
inline int dijkstra() {
memset(st, 0, sizeof st);
memset(dist, 0x3f, sizeof dist);
memset(cnt, 0, sizeof cnt);
priority_queue<Edge, vector<Edge>, greater<Edge>> heap;
heap.push({s, 0, 0});
dist[s][0] = 0;
cnt[s][0] = 1;
while (heap.size()) {
auto t = heap.top(); heap.pop();
int ver = t.ver, type = t.type, dis = t.dis;
if (st[ver][type]) continue;
st[ver][type] = 1;
for (int i = h[ver]; i; i = ne[i]) {
int j = e[i], d = dis + w[i];
if (dist[j][0] > d) {
dist[j][1] = dist[j][0], cnt[j][1] = cnt[j][0];
dist[j][0] = d, cnt[j][0] = cnt[ver][type];
heap.push({j, 0, dist[j][0]});
heap.push({j, 1, dist[j][1]});
}
else if (dist[j][0] == d) {
cnt[j][0] += cnt[ver][type];
}
else if (dist[j][1] > d) {
dist[j][1] = d;
cnt[j][1] = cnt[ver][type];
heap.push({j, 1, dist[j][1]});
}
else if (dist[j][1] == d) {
cnt[j][1] += cnt[ver][type];
}
}
}
int res = cnt[f][0];
// 判断次短路是否满足条件
if (dist[f][0] + 1 == dist[f][1]) res += cnt[f][1];
return res;
}
inline void solve() {
scanf("%d%d", &n, &m);
while (m --) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
scanf("%d%d", &s, &f);
printf("%d\n", dijkstra());
}
int main() {
int _T;
scanf("%d", &_T);
while (_T --) {
memset(h, 0, sizeof h);
idx = 0;
solve();
}
return 0;
}