Loading

单源最短路的拓展应用

1137. 选择最佳线路 - AcWing题库

最短路、虚拟原点

两种做法

  1. 建立虚拟远点,假设从点0出发,能到达所有起点,求最短路

  2. 反向建图,将朋友家作为起点,选取朋友家到所有出发站点的最短距离

方法一:

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]表示次短路的方案数,那么就有:

  1. 边界:cnt[s][0] = 1, cnt[s][1] = -1

  2. 最短路更新dist[i][0] > w:之前的最短路的状态转移到次短路,更新当前最短路的状态

  3. 属于最短路的方案dist[i][0] == w:统计最短路的方案

  4. 次短路更新dist[1][0] > w:更新次短路的状态

  5. 属于次短路的方案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;
}
posted @ 2022-02-28 19:01  Frank_Ou  阅读(19)  评论(0编辑  收藏  举报