分层图最短路
分层图最短路
定义:
顾名思义,“分层图最短路”就是在多层平行的图上跑最短路
模型:
分层图最短路的模型就是在最短路模型的基础上加上k个决策
最短路模型:给定n个点m个条路,求从s出发到t的最短距离
分层图最短路模型:给定n个点m条路以及k个决策,再求出s到t的最短距离
k个决策不会影响图的结构,只会影响当前的代价或状态
(PS:对于每一道题,决策的具体内容是不一样的,可以结合之后的例题理解)
模板:
面对分层图最短路这种题,我们一般有两种方法解决:
-
直接构建k+1层平行的图
-
多开一维记录决策信息
- 现在来讲第一种方法
-
面对每一层,我们还是先像普通最短路一样连边建图
-
处理层与层,我们将有边相连的两个点u、v各自多向下一层连一条边(权值视题目的决策内容而定,并且是有向边,向下的有向边,这样就模拟出了k次决策!)
-
当有n个点时,(1 ~ n)表示第一层,(1+n)~(n+n)为第二层,(1+2 * n)~(n+2 * n)为第三层·······(1+i * n)~(n+i * n)为第i+1层
-
由第3点可得:因为要建k+1层图,所以数组要开到n * ( k + 1),点的个数也为n * ( k + 1 )
有点抽象,我们来举个栗子:
n=4,m=3,k=2
0 1 100
1 2 100
2 3 100
建成的k+1层图如下:
现在给出完整的模板code:
#include <bits/stdc++.h>
using namespace std;
int n,m,k,u,v,w,s,t,tot;
int dis[5000010],vis[5000010],head[5000010]; //根据题意改变数组大小
priority_queue<pair<int,int> > shan;
struct node {
int to,net,val;
}e[5000010];
inline void add(int u,int v,int w) { //链式前向星存边
e[++tot].val=w;
e[tot].to=v;
e[tot].net=head[u];
head[u]=tot;
}
inline void dijkstra(int s) { //Dijkstra跑最短路的板子
memset(dis,0x3f,sizeof dis);
dis[s]=0;
shan.push(make_pair(0,s));
while(!shan.empty()) {
int x=shan.top().second;
shan.pop();
if(vis[x]) continue;
vis[x]=1;
for(register int i=head[x];i;i=e[i].net) {
int v=e[i].to;
if(dis[v]>dis[x]+e[i].val) {
dis[v]=dis[x]+e[i].val;
shan.push(make_pair(-dis[v],v));
}
}
}
}
int main() {
scanf("%d%d%d",&n,&m,&k);
scanf("%d%d",&s,&t);
for(register int i=1;i<=m;i++) {
scanf("%d%d%d",&u,&v,&w);
add(u,v,w); //正常存边
add(v,u,w);
for(register int j=1;j<=k;j++) { //构建之后的k层图
add(u+j*n,v+j*n,w); //每一层的连边同上正常连边
add(v+j*n,u+j*n,w);
add(u+(j-1)*n,v+j*n,0); //层与层之间的联系
add(v+(j-1)*n,u+j*n,0); //层与层边的权值不一定是0!要视题目而定
}
}
for(register int i=1;i<=k;i++) { //将每一层的终点特别连起来
add(t+(i-1)*n,t+i*n,0);
}
dijkstra(s);
printf("%d",dis[t+k*n]); //最终答案存在最后一层的终点处
return 0;
}
- 第二种做法
因为我认为第一种做法比较简单,就没怎么编写第二种做法的代码(而且两种做法面对不卡数据的题目选任意一种都能过)
所以现在就给出我学习的博客,大家可以看这个链接自行学习第二种做法(个人感觉有点类似于DP思想)
PS:我的第一种做法代码和上面的博客有些许的区别,希望大家区分开来,不要记混了qwq
例题:
-
这道题完全就是分层图最短路题型的模板题
-
注意一点就是这题的编号是从0(n-1)的,所以为了处理方便,我们在输入后就进行**加一操作,转换为1n的编号**
-
现在给出主程序代码(Dijkstra部分见上面的模板):
int main() {
scanf("%lld%lld%lld",&n,&m,&k);
scanf("%lld%lld",&s,&t);
s++;t++; //因为编号从0开始,方便处理都加1,下面的u++、v++同理
for(register long long i=1;i<=m;i++) {
scanf("%lld%lld%lld",&u,&v,&w);
u++;v++;
add(u,v,w);
add(v,u,w);
for(register long long j=1;j<=k;j++) {
add(u+j*n,v+j*n,w);
add(v+j*n,u+j*n,w);
add(u+(j-1)*n,v+j*n,0);
add(v+(j-1)*n,u+j*n,0); //因为该题的决策时免费,所以权值为0
}
}
for(register long long i=1;i<=k;i++) {
add(t+(i-1)*n,t+i*n,0);
}
dijkstra(s);
printf("%lld",dis[t+k*n]);
return 0;
}
-
这题也是直接套模板就能A掉的,而且规定了起点是1终点是n,双倍经验get!
-
哦哦哦,补充一下,题意就是求从1到n的最短路距离,不是输出对哪些小径进行升级ovo!
-
直接给主程序代码:
int main() {
scanf("%d%d%d",&n,&m,&k);
for(register int i=1;i<=m;i++) {
scanf("%d%d%d",&u,&v,&t);
add(u,v,t);
add(v,u,t);
for(register int j=1;j<=k;j++) {
add(u+j*n,v+j*n,t);
add(v+j*n,u+j*n,t);
add(u+(j-1)*n,v+j*n,0);
add(v+(j-1)*n,u+j*n,0);
}
}
for(register int i=1;i<=k;i++) {
add(i*n,(i+1)*n,0); //因为每一层的终点就是n,所以改写成这样,注意一下区别ovo
}
dijkstra();
printf("%d",dis[(k+1)*n]);
return 0;
}
-
这道题95%都是板子,只有一点不同:本题的决策内容是花费减半,所以层与层之间的权值不再是0,而是这条边原本权值的一半!
-
其他的就没什么好说的,三倍经验get!
-
给出主程序代码如下:
int main() {
scanf("%d%d%d",&n,&m,&k);
for(register int i=1;i<=m;i++) {
scanf("%d%d%d",&u,&v,&t);
add(u,v,t);
add(v,u,t);
for(register int j=1;j<=k;j++) {
add(u+j*n,v+j*n,t);
add(v+j*n,u+j*n,t);
add(u+(j-1)*n,v+j*n,t/2); //注意区别哦!这里的权值不再是0,而是一半的花费!
add(v+(j-1)*n,u+j*n,t/2);
}
}
for(register int i=1;i<=k;i++) {
add(i*n,(i+1)*n,0); //同上一道题,因为每一层的终点就是n,所以改写成这样
}
dijkstra();
printf("%d",dis[(k+1)*n]);
return 0;
}
-
初看这道题容易直接当做纯板子题,但是你会发现程序过不了样例:答案是4,自己的输出是5
-
再去读题,请注意这句话:“总费用决定于其中最长的电话线的长度”,说明不是求从1到n的最短路,而是求从1到n的路径中最大边权最小,所以我们需要改一下Dijkstra的入队判断:
if(dis[v]>max(dis[x],e[i].val)) {
dis[v]=max(dis[x],e[i].val);
shan.push(make_pair(-dis[v],v));
}
- 然后不要忘记有输出-1这种情况,所以我们需要在输出前加一个判断:
if(dis[(k+1)*n]>1000001) printf("-1");
else printf("%d",dis[(k+1)*n]);
因为每条路的边权值不会超过1000000(题目规定)
- 剩下的跟以上题目代码一样,就不给出代码了,四倍经验get!(注意一下,这道题的决策内容也是免费,所以层与层的边权值为0即可)
最后,以上只是我对于“分层图最短路”的基本学习记录,有任何理解错误的地方,还烦请各位dalao指出,蒟蒻感激不尽啊orz!