分层图最短路

分层图最短路

定义:

顾名思义,“分层图最短路”就是在多层平行的图上跑最短路

模型:

分层图最短路的模型就是在最短路模型的基础上加上k个决策

最短路模型:给定n个点m个条路,求从s出发到t的最短距离

分层图最短路模型:给定n个点m条路以及k个决策,再求出s到t的最短距离

k个决策不会影响图的结构,只会影响当前的代价或状态

(PS:对于每一道题,决策的具体内容是不一样的,可以结合之后的例题理解)


模板:

面对分层图最短路这种题,我们一般有两种方法解决:

  1. 直接构建k+1层平行的图

  2. 多开一维记录决策信息

  • 现在来讲第一种方法
  1. 面对每一层,我们还是先像普通最短路一样连边建图

  2. 处理层与层,我们将有边相连的两个点u、v各自多向下一层连一条边(权值视题目的决策内容而定,并且是有向边,向下的有向边,这样就模拟出了k次决策!

  3. 当有n个点时,(1 ~ n)表示第一层,(1+n)~(n+n)为第二层,(1+2 * n)~(n+2 * n)为第三层·······(1+i * n)~(n+i * n)为第i+1层

  4. 由第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


例题:

  1. 这道题完全就是分层图最短路题型的模板题

  2. 注意一点就是这题的编号是从0(n-1)的,所以为了处理方便,我们在输入后就进行**加一操作,转换为1n的编号**

  3. 现在给出主程序代码(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;
}
  1. 这题也是直接套模板就能A掉的,而且规定了起点是1终点是n,双倍经验get!

  2. 哦哦哦,补充一下,题意就是求从1到n的最短路距离,不是输出对哪些小径进行升级ovo!

  3. 直接给主程序代码:

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;
}
  1. 这道题95%都是板子,只有一点不同:本题的决策内容是花费减半,所以层与层之间的权值不再是0,而是这条边原本权值的一半!

  2. 其他的就没什么好说的,三倍经验get!

  3. 给出主程序代码如下:

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;
}
  1. 初看这道题容易直接当做纯板子题,但是你会发现程序过不了样例:答案是4,自己的输出是5

  2. 再去读题,请注意这句话:“总费用决定于其中最长的电话线的长度”,说明不是求从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. 然后不要忘记有输出-1这种情况,所以我们需要在输出前加一个判断:
if(dis[(k+1)*n]>1000001) printf("-1");
else printf("%d",dis[(k+1)*n]);

因为每条路的边权值不会超过1000000(题目规定)
  1. 剩下的跟以上题目代码一样,就不给出代码了,四倍经验get!(注意一下,这道题的决策内容也是免费,所以层与层的边权值为0即可

最后,以上只是我对于“分层图最短路”的基本学习记录,有任何理解错误的地方,还烦请各位dalao指出,蒟蒻感激不尽啊orz!


posted @ 2020-06-21 20:37  Eleven谦  阅读(532)  评论(1编辑  收藏  举报