zoukankan      html  css  js  c++  java
  • 分层图最短路

    分层图最短路

    定义:

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

    模型:

    分层图最短路的模型就是在最短路模型的基础上加上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)的,所以为了处理方便,我们在输入后就进行加一操作,转换为1~n的编号

    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!


  • 相关阅读:
    node设置res.cookie跨域问题解决
    Rails常用插件
    什么是编程基础
    【数学之美】抽屉原理
    时分秒针重合问题
    如何理解【业务逻辑】
    【转】有关环境变量的若干问题?
    关于指针类型转化后在printf输出的问题
    rand * () 之间相互生成总结
    4.14做一个新浪微博的小东东
  • 原文地址:https://www.cnblogs.com/Eleven-Qian-Shan/p/13173897.html
Copyright © 2011-2022 走看看