分层图的妙用
介绍
分层图是一种很容易理解并且用途广泛的图,多用于求最短路,它其实是普通图最短路的加强版,可以解决一些图论题目的特殊操作,这个特殊操作就是我们去分的“层”。
思想
做分层图的题,只要把题目读懂,明白到底“层”分在哪就能解题,比如说:
给定一张\(n\)个点\(m\)条边的图,给出原点\(s\)和目标点\(t\),可以创造\(k\)次虫洞(不一定要用完),使得任意\(i\)到\(j\)的距离变为\(0\),请求出\(s\)到\(t\)的最短距离
那么,这道题有区别与普通最短路的地方明显就在于可以创造虫洞,而我们需要分的层就体现在创造虫洞的次数,下面我们就用图演示分层算法,假设此时原点为1,目标点为5,可以创造2次虫洞:
首先我们先将图复制为3份 为什么不是2份?因为一个虫洞也不创建也算一种情况
然后,对于某个点来说(这里以点4为例),它既有连接本层内的原始边(蓝色),而且我们还可以创造一次虫洞,使得它直接传送到下一个节点,因此应该还要连一条距离为\(0\)的边(红色),但是这条边应该连到\(k+1\)的图的下一个节点,表明我们创造了一次虫洞(\(k=2\)时就不能继续往上连了)
最后,我们按照这样的规律,将所有的“跨层边”都连起来(彩色边,距离都为0),跑一遍最短路,\(k=0\)时的原点到\(k=u\)时的目标点的最短路就是答案
因此,分层图仅仅通过多加边的方式解决了最短路中的“自定义”操作,其他步骤与普通最短路无异,而如果要修改最短路算法来达到相同效果,将会是非常麻烦的
实现
那么,相信你已经掌握了分层图的思想,做一道例题试试吧
P4568 [JLOI2011] 飞行路线
这里我采用了多层图存储于同一个数组中
注意本题有个hack数据,hack原理:戳我
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int MAXN=1e4+5,MAXNK=MAXN*11;
int n,m,k,s,t,dis[MAXNK];
bool vis[MAXNK];
struct EDGE{
int to,val;
};
vector<EDGE>g[MAXNK];
inline void add(int from,int to,int cost){
EDGE tmp;
tmp.to=to;tmp.val=cost;
g[from].push_back(tmp);
}
void dijkstra(){
dis[s]=0;
priority_queue<pii,vector<pii>,greater<pii>>pq;
pq.push(make_pair(0,s));
while(!pq.empty()){
int cur=pq.top().second;pq.pop();
if(!vis[cur]){
vis[cur]=1;
for(auto nxt:g[cur]){
if(dis[nxt.to]>dis[cur]+nxt.val){
dis[nxt.to]=dis[cur]+nxt.val;
pq.push(make_pair(dis[nxt.to],nxt.to));
}
}
}
}
}
int main(){
ios::sync_with_stdio(false);
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
cin>>n>>m>>k>>s>>t;
for(int i=1,from,to,cost;i<=m;i++){
cin>>from>>to>>cost;
add(from,to,cost);
add(to,from,cost);
for(int j=1;j<=k;j++){
add(from+(j-1)*n,to+j*n,0);
add(to+(j-1)*n,from+j*n,0);
add(from+j*n,to+j*n,cost);
add(to+j*n,from+j*n,cost);
}
}
for(int i=1;i<=k;++i)
{
add(t+(i-1)*n,t+i*n,0);//这行代码的目的是为了将所有层图的目标点最短路集中在第k层的汇点,hack数据就hack在这里
}
dijkstra();
cout<<dis[t+k*n]<<endl;
return 0;
}