1. 程式人生 > 其它 >P1144 最短路計數

P1144 最短路計數

參考部落格: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+2n)代表第三層 ......(1+in)~(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]飛行路線