分层图
引入
有时我们会遇到这样的问题:
给定一个无向图,你可以选择最多 \(k\) 条边将其边权变为 \(0\),求最短路。
这种问题可以使用分层图。
解释
第一眼看上去,肯定是先找到最优策略,寻找最优的 \(k\) 条边消掉,再跑最短路,但实在是太麻烦啦!
这样就陷入了窘境,那么我们可以转换一下思路,可不可以把所有状态全部列出来,再由最短路寻找最优解呢?
答案是可以的,那就是分层图。
分层图的原理就是将原图复制 \(k\) 份,图之间根据题意用边连接起来。

如图,() 内的是图的层数,第 \(x\) 层的含义为进行 \(x\) 次操作。
解释一下,<(0)1,(1)2> 这条边表示从 \(1\) 结点到 \(2\) 结点并消耗一次操作次数(看到边权为 \(0\) 了吗),
<(0)2,(1)1> 同理,它和 <(0)1,(1)2> 构成了一条双向边。
那么它的意义到底是什么呢?
看,如果从 (0)1 出发,这时只能去 (0)2 或 (1)2,也就是去 \(2\) 结点,但是选择不操作与操作,如果选择操作,到达 (1)2 就不能再继续操作了,因为此时没有其他可以走的边权为 \(0\) 的边了,这样就起到了一个限定次数的作用,而最短路算法就会自己找到最短路,在分层图中,也就是最优方案。
这样,直接在这个图上跑最短路,就可以直接得到结果了!
实现
以 JLOI2011] 飞行路线为例,建图即可,需要注意的是初始化与空间要开得够大。
#include<bits/stdc++.h>
using namespace std;
const int N = 12e4 + 20; // 别开太小
struct edge{
int v,w;
};
struct node{
int l,u;
const bool operator<(const node t) const{
return l > t.l;
}
};
int s,t,n,m,k,dis[N];
vector<edge> g[N];
void dij(int s){ // 最短路
for(int i = 0;i < N;i ++) // 初始化要足够
dis[i] = 0x3f3f3f3f;
priority_queue<node> pq;
pq.push({0,s});
dis[s] = 0;
while(pq.size()){
node f = pq.top();
pq.pop();
if(f.l > dis[f.u])
continue;
for(auto it:g[f.u])
if(f.l + it.w < dis[it.v]){
dis[it.v] = f.l + it.w;
pq.push({dis[it.v],it.v});
}
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m>>k>>s>>t;
while(m --){
int u,v,w;
cin>>u>>v>>w;
g[u].push_back({v,w}); // 原图
g[v].push_back({u,w});
for(int i = 1;i <= k;i ++){ // 分层图
g[u + (i - 1) * n].push_back({v + i * n,0});
g[v + (i - 1) * n].push_back({u + i * n,0});
g[u + i * n].push_back({v + i * n,w});
g[v + i * n].push_back({u + i * n,w});
}
}
dij(s);
int ans = 0x3f3f3f3f;
for(int i = 0;i <= k;i ++)
ans = min(ans,dis[t + i * n]);
cout<<ans;
return 0;
}

浙公网安备 33010602011771号