分层图最短路是指在可以进行分层图的图上解决最短路问题。分层图:可以理解为有多个平行的图。
一般模型是:在一个正常的图上可以进行 k 次决策,对于每次决策,不影响图的结构,只影响目前的状态或代价。一般将决策前的状态和决策后的状态之间连接一条权值为决策代价的边,表示付出该代价后就可以转换状态了。
一般有两种方法解决分层图最短路问题:
- 建图时直接建成k+1层
- 多开一维记录机会信息
当然具体选择哪一种方法,看数据范围吧 。
第一种方法:
我们建k+1层图。然后有边的两个点,多建一条到下一层边权为0的单向边,如果走了这条边就表示用了一次机会。
有N个点时,1~n表示第一层,(1+n)~(n+n)代表第二层,(1+2*n)~(n+2*n)代表第三层,(1+i*n)~(n+i*n)代表第i层。因为要建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,中的最小值。
第一种模板:
#include <bits/stdc++.h> using namespace std; struct edge { int to, cost; }; typedef pair<int, int> P; // first是最短距离,second是顶点的编号 const int MAX_V = 2000005; int n, m, s, t, k; int d[MAX_V]; vector<edge> G[MAX_V]; void dijkstra() { priority_queue<P, vector<P>, greater<P> >que; memset(d, 0x3f, sizeof(d)); d[s] = 0; que.push(P(0, s)); while (!que.empty()) { P p = que.top(); que.pop(); int v = p.second; if (d[v] < p.first) continue; for (int i = 0; i < G[v].size(); i++) { edge e = G[v][i]; if (d[e.to] > d[v] + e.cost) { d[e.to] = d[v] + e.cost; que.push(P(d[e.to], e.to)); } } } } int main() { scanf("%d%d%d%d%d", &n, &m, &s, &t, &k); for(int i = 1; i <= m; i++) { int u, v, cost; scanf("%d%d%d", &u, &v, &cost); for(int j = 0; j <= k; j++) { G[u+n*j].push_back({v+n*j,cost}); G[v+n*j].push_back({u+n*j,cost}); if(j < k) { G[u+n*j].push_back({v+n*(j+1),0}); G[v+n*j].push_back({u+n*(j+1),0}); } } } dijkstra(); int ans=0x3f3f3f3f; for(int i = 0; i <= k; i++) ans=min(ans, dis[t+n*i]); printf("%d ", ans); }
第二种方法:
我们把dis数组(和vis数组)多开一维记录k次机会的信息。
dis[ i ][ j ] 代表到达 i 用了 j 次免费机会的最小花费.
(考虑当前取出的点是某是最短距离,不是就丢弃这个点,或者用vis[ i ][ j ] 代表到达 i 用了 j 次免费机会的情况是否出现过)
更新的时候先更新同层之间(即花费免费机会相同)的最短路,然后更新从该层到下一层(即再花费一次免费机会)的最短路。
不使用机会 dis[v][t] = min(min,dis[u][t] + cost)
使用机会 dis[v][t+1] = min(dis[v][t+1],dis[u][t] + 0)
对于数据:
n = 4,m = 3, k = 2
0 1 100
1 2 100
2 3 100
建成图之后大概是这样的:
第二种模板:
#include <bits/stdc++.h> using namespace std; typedef pair<int, int> P; const int MAX_N=1005; int n, m, s, t, k; vector<P> G[MAX_N]; //first代表顶点编号,second代表权值 int d[MAX_N][MAX_N]; void dijkstra() { memset(d, 0x3f, sizeof(d)); d[s][0] = 0; priority_queue<P, vector<P>, greater<P> >que; //fisrt代表最短距离,second代表对应层数的顶点编号 que.push({0,s}); while (!que.empty()) { P p = que.front(); que.pop(); int u = p.second%n, t = p.second/n; if (d[u][t]<p.first) continue; //当前取出的最小值不是最短距离,就丢弃这个值 for (int i = 0; i < G[u].size(); i++) { int v = G[u][i].first, cost = G[u][i].second; if(d[v][t]>d[u][t]+cost) d[v][t] = d[u][t]+cost, que.push({d[v][t],t*n+v}); if(t<k&&d[v][t+1]>d[u][t]) d[v][t+1] = d[u][t], que.push({d[v][t+1],(t+1)*n+v}); } } } int main() { scanf("%d%d%d%d%d", &n, &m, &s, &t, &k); for(int i=0;i<m;i++) { int u, v, cost; scanf("%d%d%d", &u, &v, &cost); G[u].push_back({v,cost}); G[v].push_back({u,cost}); } dijkstra(); int ans=0x3f3f3f3f; for(int j = 0; j <= k; j++) ans=min(ans,d[t][j]); printf("%d ",ans); }
附上题目:P4568 [JLOI2011]飞行路线