分层图最短路

参考博客:https://blog.csdn.net/sugarbliss/article/details/86551050

分层图最短路是指在可以进行分层图的图上解决最短路问题。分层图:可以理解为有多个平行的图。

一般模型是:在一个正常的图上可以进行 k 次决策,对于每次决策,不影响图的结构,只影响目前的状态或代价。一般将决策前的状态和决策后的状态之间连接一条权值为决策代价的边,表示付出该代价后就可以转换状态了。

有两种方法解决分层图最短路问题:

1、建图时直接建成 k+1 层
2、多开一维记录机会信息

具体选哪种依题目数据范围而定

第一种方法

我们建 k+1 层图。然后有边的两个点,多建一条到下一层边权为 0 的单向边,如果走了这条边就表示用了一次机会。

有 N 个点时,1~n 表示第一层,(1+n)~(n+n)代表第二层,(1+2 * n)~(n+2 * n)代表第三层 ......(1+i * n)~(n+i * n)代表第 i+1 层。因为要建 K+1 层图,数组要开到 n * (k + 1),点的个数也为n * (k + 1 ) 。

对于数据:

n = 4,m = 3,k = 2
0   1   100
1   2   100
2   3   100

建成图之后大概是这样的:

对于上面的数据:答案就是 3,3+n,3+2n 中的最小值

板子(例题:P4568 [JLOI2011]飞行路线):

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e7 + 10;
const int maxm = 1e4 + 10;
#define pii pair<int, int>
#define INF 0x7fffffff

struct node{
  int to, nxt, val;
}e[maxn];
int n, m, s, t, k, cnt, ans = INF;
int head[maxn], dis[maxn], vis[maxn];

inline int read() {
  int x = 0, k = 1; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') k = -1;
  for (; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
  return x * k;
}

inline void init() {
  memset(head, -1, sizeof(head));
  memset(dis, 0x3f, sizeof(dis));
}

inline void add(int u, int v, int w) {
  e[++cnt].to = v;
  e[cnt].val = w;
  e[cnt].nxt = head[u];
  head[u] = cnt;
}

inline void dijkstra() {
  priority_queue<pii,vector<pii>,greater<pii> > q;
  dis[s] = 0; q.push({dis[s], s});
  while (!q.empty()) {
    int now = q.top().second; q.pop();
    if (vis[now]) continue;
    vis[now] = 1;
    for (int i = head[now]; i != -1; i = e[i].nxt) {
      int v = e[i].to;
      if (!vis[v] && dis[v] > dis[now] + e[i].val) {
        dis[v] = dis[now] + e[i].val;
        q.push({dis[v], v});
      }
    }
  }
}

int main() {
  init();
  n = read(); m = read(); k = read();
  s = read(); t  =read();
  while (m--) {
    register int u, v, w;
    u = read(); v = read(); w = read();
    for (int i = 0; i <= k; i++) {
      add(u + i * n, v + i * n, w);
      add(v + i * n, u + i * n, w);
      if (i != k) {
        add(u + i * n, v + (i + 1) * n, 0);
        add(v + i * n, u + (i + 1) * n, 0);
      }
    }
  }
  dijkstra();
  for (int i = 0; i <= k; i++)
    ans = min(ans, dis[t + i * n]);
  printf("%d\n", ans);
  return 0;
}

第二种方法

我们把 dis 数组和 vis 数组多开一维记录k次机会的信息。

  • dis[i][j] 代表到达 i 用了 j 次免费机会的最小花费
  • vis[i][j] 代表到达 i 用了 j 次免费机会的情况是否出现过

更新的时候先更新同层之间(即花费免费机会相同)的最短路,然后更新从该层到下一层(即再花费一次免费机会)的最短路。

  • 不使用机会 dis[v][c] = min(min,dis[now][c] + edge[i].w)
  • 使用机会 dis[v][c+1] = min(dis[v][c+1],dis[now][c])

对于数据:

n = 4,m = 3,k = 2
0   1   100
1   2   100
2   3   100

建成图之后大概是这样的:

板子(例题同上):

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 1e6 + 10;
const int maxm = 1e4 + 10;
#define pii pair<int, int>
#define INF 0x7fffffff

struct node{
  int to, nxt, val, cost;
}e[maxn];
int n, m, s, t, k, cnt, ans = INF;
int head[maxn], dis[maxn][15], vis[maxn][15];

inline int read() {
  int x = 0, k = 1; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') k = -1;
  for (; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
  return x * k;
}

inline void init() {
  memset(head, -1, sizeof(head));
  memset(dis, 0x3f, sizeof(dis));
}

inline void add(int u, int v, int w) {
  e[++cnt].to = v;
  e[cnt].val = w;
  e[cnt].nxt = head[u];
  head[u] = cnt;
}

inline void dijkstra() {
  priority_queue<pii,vector<pii>,greater<pii> > q;
  dis[s][0] = 0; q.push({dis[s][0], s});
  while (!q.empty()) {
    int now = q.top().second; q.pop();
    int c = now / n; now %= n;
    if (vis[now][c]) continue;
    vis[now][c] = 1;
    for (int i = head[now]; i != -1; i = e[i].nxt) {
      int v = e[i].to;
      if (!vis[v][c] && dis[v][c] > dis[now][c] + e[i].val) {
        dis[v][c] = dis[now][c] + e[i].val;
        q.push({dis[v][c], v + c * n});
      }
    }
    if (c < k) {
      for (int i = head[now]; i != -1; i = e[i].nxt) {
        int v = e[i].to;
        if (!vis[v][c + 1] && dis[v][c + 1] > dis[now][c]) {
          dis[v][c + 1] = dis[now][c];
          q.push({dis[v][c + 1], v + (c + 1) * n});
        }
      }
    }
  }
}

int main() {
  init();
  n = read(); m = read(); k = read();
  s = read(); t  =read();
  while (m--) {
    register int u, v, w;
    u = read(); v = read(); w = read();
    add(u, v, w); add(v, u, w);
  }
  dijkstra();
  for (int i = 0; i <= k; i++)
    ans = min(ans, dis[t][i]);
  printf("%d\n", ans);
  return 0;
}

分层图最短路例题:

P4822 [BJWC2012]冻结

P4568 [JLOI2011]飞行路线

P2939 [USACO09FEB]Revamping Trails G

posted @ 2021-08-01 20:52  Moominn  阅读(208)  评论(0编辑  收藏  举报