分层图

引入

有时我们会遇到这样的问题:

给定一个无向图,你可以选择最多 \(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;
}
posted @ 2023-12-12 22:13  ManGo_Mouse  阅读(49)  评论(0)    收藏  举报