分层图的模板和最短路一样,正所谓图论的99%难点都在建图。
一般模型是:在一个正常的图上可以进行 k 次决策,对于每次决策,不影响图的结构,只影响目前的状态或代价。一般将决策前的状态和决策后的状态之间连接一条权值为决策代价的边,表示付出该代价后就可以转换状态了。
用分层图的几种情况:
1、 有k个不同集合的边,将每个集合内的边建成一张图,再建立第k+1个图,是一个虚层,用这个虚层将这k张图连接起来。每次可以通过虚层转移到另一个集合的图中。如例1.小雨坐地铁。
2、 有k个机会使得走当前此边不花费代价或花费特殊的代价,可以建立k张相同的该图,每张图之间用有边的点连接起来,其代价是0或是特殊的值。每向下走一层,就代表用了一次机会,使得当前的路花费为0,最多可以走k次。如例2.飞行路线。
3、 有k个机会逆向行驶,我们可以建k张相同的该图,将每层图之间有边的两个点用的逆向的边连接。每向下走一层,就代表用了一次机会逆向走了一次,最多可以走k次。
建k+1层图是一种方法
也可以把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]);
牛客小于坐地铁https://ac.nowcoder.com/acm/problem/26257
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int INF = 0x3f3f3f3f;
const int maxn = 5e5 + 10;
vector<P>v[maxn];
int dis[maxn];
bool vis[maxn];
priority_queue<P, vector<P>, greater<P> > q;
int n,m,s,t,a,b,c,k,x;
void add(int x,int y,int z) {
v[x].push_back(make_pair(z,y));
}
void Dijkstra(int s) {
memset(dis,INF,sizeof(dis));
memset(vis,0,sizeof(vis));
dis[s] = 0;
q.push(P(0,s));
while(!q.empty()) {
P p = q.top(); q.pop();
int u = p.second;
if(vis[u]) continue;
vis[u] = 1;
for(int i = 0;i < v[u].size();i++) {
int vi = v[u][i].second;
int w = v[u][i].first;
if(!vis[vi]) {
if(dis[vi] > dis[u] + w) {
dis[vi] = dis[u] + w;
q.push(P(dis[vi],vi));
}
}
}
}
}
int main() {
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i = 1;i <= m; ++i) {
scanf("%d%d%d",&a,&b,&c);
k = -1;
for(int j = 1;j <= c; ++j) {
scanf("%d",&x);
if(~k) {
add((i - 1) * n + x,(i - 1) * n + k,b);
add((i - 1) * n + k,(i - 1) * n + x,b);
}
add(n * m + x,(i - 1) * n + x,a);
add((i - 1) * n + x,n * m + x,0);
k = x;
}
}
Dijkstra(n * m + s);
// for(int i = 1;i <= (n + 1) * m; ++i) cout<<dis[i]<<' ';cout<<endl;
if(dis[n * m + t] == INF) printf("-1\n");
else printf("%d\n",dis[n * m + t]);
return 0;
}