顾名思义,“分层图最短路”就是在多层平行的图上跑最短路node
分层图最短路的模型就是在最短路模型的基础上加上k个决策c++
最短路模型:给定n个点m个条路,求从s出发到t的最短距离数组
分层图最短路模型:给定n个点m条路以及k个决策,再求出s到t的最短距离学习
k个决策不会影响图的结构,只会影响当前的代价或状态spa
(PS:对于每一道题,决策的具体内容是不同的,能够结合以后的例题理解).net
面对分层图最短路这种题,咱们通常有两种方法解决:code
直接构建k+1层平行的图blog
多开一维记录决策信息get
面对每一层,咱们仍是先像普通最短路同样连边建图博客
处理层与层,咱们将有边相连的两个点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)的,因此为了处理方便,咱们在输入后就进行加一操做,转换为1~n的编号
如今给出主程序代码(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)); }
if(dis[(k+1)*n]>1000001) printf("-1"); else printf("%d",dis[(k+1)*n]); 由于每条路的边权值不会超过1000000(题目规定)
最后,以上只是我对于“分层图最短路”的基本学习记录,有任何理解错误的地方,还烦请各位dalao指出,蒟蒻感激涕零啊orz!