洛谷 P4822 题解(分层图)

洛谷 P4822 题解

蒟蒻的第一篇题解 有问题和不足欢迎指出

原题(P4822 BJWC2012 冻结 - 洛谷)

简化题目

大路上有 N 个城市(编号为 1 ~ N),M 条双向的道路。有 K 张卡片可以使通过一条道路的时间减少到原先的一半(题目保证每条道路的原始花费为偶数)。求出在使用不超过 K 张卡片的情况下,从城市 1 ~ N 的最少耗时。

思路

乍一看是一道普通的最短路,但是如果按照题目对于每一条边再多建一条耗时为原来的一半的边去跑最短路,就会发现最短路算法只会选择耗时为原来的一半的边,导致用了超过 K 张卡片。

总结一下目前的问题:如果只在一张图中建边,就会导致边混乱,超出 K 的限制。

那有没有一种做法使得可以同时保留原耗时与用了卡片的耗时,还可以确保只用 K 张卡片?

分层图

字面意思,即将图根据阶段分层,每层都保持原有图的结构,且每层之间都有互通的边,以满足题目要求的限制

在本题中,我们可以根据 "最多使用 K 张卡片" 来划分阶段,未使用卡片时对应第 0 个阶段,使用 1 张卡片对应第 1 个阶段,以此类推。对于每一个阶段,建一张与原有图结构相同的图,并在每层图之间建耗时为原来的一半的边,这样就可以使得只要走耗时为原来的一半的边,就可以下到下一层,而总共只有 K 层,这就满足了最多使用 K 张卡片的限制,且因为每一层的图中都有原始花费的边,就使得在每层中即使不下到下一层,也可以按照原耗时跑最短路,求得用 i 张卡片的最小耗时( i 为当前所在的层数)。

具体做法

因为需要建 K 层图,所以在读入边时可以增加一层 0 ~ K 的循环,对于每个循环到的数字 i ,本层(第 i 层)建边对应的点为 i * n+a 与 i * n+b(a,b为原本就需建边的两点),同时,需要向 第 i + 1 层建耗时为原来的一半的边(注意边界)。然后正常跑最短路,统计每层到终点的耗时取最小值即可。

代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define ar array<int, 2>
int main()
{
	int n, m, k;
	cin >> n >> m >> k;
	vector<vector<ar>> e((k+1)*n+1, vector<ar>());
	for(int i=1;i<=m;++i){
		int a, b, c;
		cin >> a >> b >> c;
		for(int j=0;j<=k;++j){
			int na=j*n+a, nb=j*n+b;
			e[na].push_back({nb, c}), e[nb].push_back({na, c});
			if(j<k) e[na].push_back({nb+n, c/2}), e[nb].push_back({na+n, c/2});
		}
	}
	vector<int> dis((k+1)*n+1, 0x3f3f3f3f);
	
	auto dijkstra = [&](int st){
		priority_queue<ar, vector<ar>, greater<ar>> q;
		vector<int> vis((k+1)*n+1, 0);
		dis[st]=0;
		q.push({dis[st], st});
		while(!q.empty()){
			int u=q.top()[1]; q.pop();
			if(vis[u]) continue;
			vis[u]=1;
			for(auto temp : e[u]){
				int v=temp[0], w=temp[1];
				if(dis[u]+w<dis[v]){
					dis[v]=dis[u]+w;
					q.push({dis[v], v});
				}
			}
		}
	};
	
	dijkstra(1);
	int ans=INT_MAX;
	for(int i=0;i<=k;++i){
		ans=min(ans, dis[i*n+n]);
	}
	cout << ans << endl;
	return 0;
}