学习笔记——浅谈分层图

学习笔记——浅谈分层图

学习笔记——浅谈分层图

许多

·

2023-10-04 21:10:44

·

算法·理论

前言

本人 APIO T1 分层图写挂爆零了,于是复习完后写下了这篇日报。

前置芝士

最短路(相信点进这个文章的人应该都会)

我们引入一道例题(模板题)来了解分层图的功能:P4822。

题目大意:给定一个图,你可以在经过一条边时让它的边权变成一半,这样的操作最多 k 次,求从 1 到 n 的最短路。

我们发现将边权变为一半这个操作非常麻烦,于是我们去看数据范围。

分层图其实非常好理解,他特别像一个三维的楼梯(个人理解)。

对于一条边,我们在每一层都连一条这样的边,**并在每一层向下一层连一条边权为一半的边**。我们一共建 $k+1$ 层,这样它最多下降 $k$ 次,对应着 $k$ 次操作。

我们口胡一个非常简单的图:

![](https://cdn.luogu.com.cn/upload/image_hosting/bckvj4zi.png)

当 $k=1$ 时,图是这样的:

![](https://cdn.luogu.com.cn/upload/image_hosting/xihd7w4s.png)

~~图画的丑,见谅~~。

然后我们在这个图上跑最短路,答案就是每一层的 $n$ 节点的最小值。

还是很难理解吗?那就放一点代码吧。

```cpp

struct node{

int to,w;

};

//建边

for(int i=1;i<=m;i++){//vector版

int u=read(),v=read(),w=read();

for(int j=0;j

b[u+j*n].push_back((node){v+j*n,w});b[u+j*n].push_back((node){v+(j+1)*n,w/2});

b[v+j*n].push_back((node){u+j*n,w});b[v+j*n].push_back((node){u+(j+1)*n,w/2});

}

b[u+k*n].push_back((node){v+k*n,w});

b[v+k*n].push_back((node){u+k*n,w});

}

for(int i=1;i<=m;i++){//链式前向星版

int u=read(),v=read(),w=read();

for(int j=0;j

add(u+j*n,v+j*n,w);add(u+j*n,v+(j+1)*n,w/2);

add(v+j*n,u+j*n,w);add(v+j*n,u+(j+1)*n,w/2);

}

add(u+k*n,v+k*n,w);

add(v+k*n,u+k*n,w);

}

//最短路跑完后的答案

int MIN=1000000000;

for(int i=0;i<=k;i++){

MIN=min(MIN,f[(i+1)*n]);

}

cout<

//这里就不放完整代码了,分层图的核心代码说实话不多,剩下就是最短路了,这里也是相信大家都会。

```

代码中的细节还是有的,因为每层都有 $n$ 个点,所以第二层的第 $i$ 个点就是 $n+i$,第 $j$ 层的第 $i$ 个点就是 $(j+1)n+i$。

建议大家用链式前向星存边,写丑的邻接表代码真的会被卡,~~别问我怎么知道~~。

------------

相信聪明的你很快就 A 了这道题,我们来到第二题:[P4568](https://www.luogu.com.cn/problem/P4568)。

这道题虽然是蓝题,但跟上面那道并没有太大的区别,不过建图的时候注意连向下一层的边权是 $0$。

```cpp

for(int i=1;i<=m;i++){//vector版

int u=read()+1,v=read()+1,w=read();//本题点是从0到n-1

for(int j=0;j

b[u+j*n].push_back((node){v+j*n,w});b[u+j*n].push_back((node){v+(j+1)*n,0});

b[v+j*n].push_back((node){u+j*n,w});b[v+j*n].push_back((node){u+(j+1)*n,0});

}

b[u+k*n].push_back((node){v+k*n,w});

b[v+k*n].push_back((node){u+k*n,w});

}

for(int i=1;i<=m;i++){//链式前向星版

int u=read()+1,v=read()+1,w=read();

for(int j=0;j

add(u+j*n,v+j*n,w);add(u+j*n,v+(j+1)*n,0);

add(v+j*n,u+j*n,w);add(v+j*n,u+(j+1)*n,0);

}

add(u+k*n,v+k*n,w);

add(v+k*n,u+k*n,w);

}

```

### ~~[自掘坟墓 APIO2023 T1 P9370](https://www.luogu.com.cn/problem/P9370)~~

~~虽然我被薄纱了~~,但这题确实是分层图好题。

这个题有两种特殊功能,一个可以让当前总通过时间为 $0$。一个拥有让当前总通行时间除以 $2$ 的能力,除以二的能力最多用 $k$ 次。

我们先看第一个特殊能力,也就是 $arr[i]=0$,实际上就是把这个点当做起点跑最短路,但注意题目还有一个要求,就是一旦到达重点,就不能移动到其他任何地方。所以我们先跑一遍 DFS 找到所有不经过终点就能到达的点,如果终点不能到达,输出 $-1$,如果能到达,将起点和 $arr[i]=0$ 的点放入队列跑多源最短路。

我们再看第二种特殊能力,也就是 $arr[i]=2$,很显然的想法,我们对于每个与这种点连边的点,向下一层的 $i$ 连一个特殊的边,经过这条边时就将总通过时间除以二,但有一点,就是他是将**总时间**除以二,所以我们在跑 dijkstra 时应当**以层数以第一关键词,边权为第二关键词**,因为对于下一层的点,**可能到达这个点所花费的时间要低于上一层的点**,这就跟负边权类似。为了避免这种情况,我们要将层数以第一关键词,边权为第二关键词,因为上一层的最短路是相对独立的,我们可以先将上层的最短路跑完,再去跑下一层的最短路。

这题说起来比较复杂,代码上也有很多细节,建议大家在**建边的时候**终点不向其他点连边。

这题有点难度,放完整代码。

# code

```cpp

#include

#include

//#include "cyberland.h"

#define n 100010

#define k 70

#define ar(now) arr[now-1]

using namespace std;

struct node{

int to,w,next;

}b[n*300];

int cnt=0;

struct node2{

int to;

double fnow;

};

const double inf=1e24;

int nn,hh;

bool operator<(node2 a1,node2 a2){

if((a1.to-1)/nn==(a2.to-1)/nn)return a1.fnow>a2.fnow;

return (a1.to-1)/nn>(a2.to-1)/nn;

}

bool operator>(node2 a1,node2 a2){

if((a1.to-1)/nn==(a2.to-1)/nn)return a1.fnow

return (a1.to-1)/nn<(a2.to-1)/nn;

}

double f[n*k];

int vis[n*k],head[n*k];

void add(int u,int v,int w){

b[cnt].to=v;

b[cnt].w=w;

b[cnt].next=head[u];

head[u]=cnt++;

}

priority_queueq;

inline void dfs(int now){

vis[now]=1;

if(now==hh)return ;

for(int i=head[now];i!=-1;i=b[i].next)

if(!vis[b[i].to]&&b[i].w>0)

dfs(b[i].to);

return ;

}

double solve(int N, int M, int K, int H, std::vector x, std::vector y, std::vector c, std::vector arr){

K=min(K,68);

H++;

nn=N;hh=H;

for(int i=0;i

x[i]++,y[i]++;

for(int i=1;i<=(N+1)*(K+1);i++)f[i]=inf,vis[i]=0,head[i]=-1;

for(int i=0;i

for(int j=0;j<=K;j++){

if(x[i]!=H)add(j*N+x[i],j*N+y[i],c[i]);

if(y[i]!=H)add(j*N+y[i],j*N+x[i],c[i]);

if(ar(x[i])==2&&j!=K&&y[i]!=H)

add(j*N+y[i],(j+1)*N+x[i],-c[i]);

if(ar(y[i])==2&&j!=K&&x[i]!=H)

add(j*N+x[i],(j+1)*N+y[i],-c[i]);//这里我对于向下一层的的边连负边权,表示需要/2,具体见最短路

}

}

dfs(1);

if(!vis[H])return -1;

for(int i=1;i<=N;i++){

if((vis[i]&&ar(i)==0)||i==1)

f[i]=0,q.push((node2){i,0});

vis[i]=0;

}

while(!q.empty()){

int now=q.top().to;q.pop();

if(vis[now])continue;

vis[now]=1;

for(int i=head[now];i!=-1;i=b[i].next){

double d=(b[i].w<0? (f[now]-b[i].w)*1.0/2.0:f[now]+b[i].w);

//如果边权是负的,我们就需要将总时间/2

if(d

f[b[i].to]=d;

if(!vis[b[i].to])q.push((node2){b[i].to,d});

}

}

}

double ans=inf;

for(int i=0;i<=K;i++)ans=min(ans,f[H+i*N]);

return ans;

}

```

闲话:邻接表被卡了,真的很慢。

[邻接表](https://www.luogu.com.cn/record/128059465)。

[链式前向星](https://www.luogu.com.cn/record/128063213)。

### 浅浅总结一下

分层图不难掌握,它将原本 $n$ 个点转成了 $n(k+1)$ 个点,在做题时一定要注意数据范围。这里还是不推荐大家用 SPFA,真的很容易被卡。

### 课后例题:

[P2939](https://www.luogu.com.cn/problem/P2939) 这题跟上面第二题没什么区别,你可以当双倍经验做,但如果真的想学习分层图,请自觉。

[P1948](https://www.luogu.com.cn/problem/P1948) 基本就是模板题。

[P3119](https://www.luogu.com.cn/problem/P3119) 这道题是分层图与其他算法的综合应用并没有太大难度。

[嘲讽作者](https://www.luogu.com.cn/problem/P9370)。

🎈 相关推荐

一年中哪几个月入手二手车才最划算?
365bet的网站是多少

一年中哪几个月入手二手车才最划算?

📅 08-01 👀 1317
中国烟草的历史起源与发展
预付365商城下载

中国烟草的历史起源与发展

📅 08-24 👀 3514
《楚留香手游》传功作用详解
预付365商城下载

《楚留香手游》传功作用详解

📅 09-02 👀 4817