分层图最短路

· · 个人记录

先占坑

首先我们先了解一下什么是分层图

就是说 我们建的图一般是只有一层的

那么我们看一张图就可以知道分层图的概念

图片如下:

没错分层图就是这么简单

干讲这个其实没什么用 那么我们结合一道例题:

JLOI2011 飞行路线

题目描述

Alice和Bob现在要乘飞机旅行,他们选择了一家相对便宜的航空公司。该航空公司一共在nn个城市设有业务,设这些城市分别标记为00到n-1n−1,一共有mm种航线,每种航线连接两个城市,并且航线有一定的价格。

Alice和Bob现在要从一个城市沿着航线到达另一个城市,途中可以进行转机。航空公司对他们这次旅行也推出优惠,他们可以免费在最多kk种航线上搭乘飞机。那么Alice和Bob这次出行最少花费多少?

输入输出格式

输入格式:

数据的第一行有三个整数,n,m,k,分别表示城市数,航线数和免费乘坐次数。

第二行有两个整数,s,t,分别表示他们出行的起点城市编号和终点城市编号。

接下来有m行,每行三个整数,a,b,c,表示存在一种航线,能从城市a到达城市b,或从城市b到达城市a,价格为c。

输出格式:

只有一行,包含一个整数,为最少花费。

其实这道题看起来也是一个普通的最短路问题

但是我们分析一下题目

我们有k个操作

是可以免费搭乘

那么我们第一种思路就是简单的暴力枚举

枚举哪一种不选

然后最后取一个最小值

这种算法十分暴力 只能得到局部分数

那我们来考虑正解

没从也就是这篇Blog的核心

分层图

我们建立一个分层图一共有k层

第i层表示选择i次免费航线

那么我们在执行最短路的时候我们可以进行一次抉择(类似于动态规划)

到当前这一阶段时我们是否要进行使用免费航行

然后最后选择一个到终点的最优解就可以了

建图过程就是先正常建图

然后在不同的层之间建立一条免费航线 代表选择免费航行

那么的话我们建图跑一个最短路

那么我们最短路要选则哪一种算法呢??

SPFA!

对不起卡我的SPFA

那么我们只能用dij+堆优化

来过这道题了

所以贴一下代码

#include  <bits/stdc++.h>
using namespace std;
const int N=1E7+10;
const int MAXX=2147483647;
struct Edge{
    int next,to,w;
}e[N<<1];
int head[N<<1],dis[N<<1],n,m,cnt,st,bg,k;
bool vis[N];
inline void add(int a,int b,int w){
    cnt++;
    e[cnt].next=head[a];
    e[cnt].w=w;
    e[cnt].to=b;
    head[a]=cnt;
}
inline void dij(int s){
    memset(dis,0x3f3f,sizeof(dis));
    priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
    q.push(make_pair(0,s));
    dis[s]=0;
    while(!q.empty()){
        int now=q.top().second;
        q.pop();
        if(!vis[now]){
            vis[now]=1;
            for(int i=head[now];i;i=e[i].next){
                int v=e[i].to;
                if(dis[v]>dis[now]+e[i].w){
                    dis[v]=dis[now]+e[i].w;
                    q.push(make_pair(dis[v],v));

                }
            }
        }   
    }
}
int main(){
    scanf("%d%d%d",&n,&m,&k);
    scanf("%d%d",&st,&bg); 
 for(int i=1;i<=m;++i)
    {
        int u,v,c;
       scanf("%d%d%d",&u,&v,&c);
        add(u,v,c);
        add(v,u,c);
        for(int j=1;j<=k;++j)
        {
            add(u+(j-1)*n,v+j*n,0);
            add(v+(j-1)*n,u+j*n,0);
            add(u+j*n,v+j*n,c);
            add(v+j*n,u+j*n,c);
        }
    }
    for(int i=1;i<=k;i++)
    {
    add(bg+(i-1)*n,bg+i*n,0);}
    dij(st);
    printf("%d",dis[bg+k*n]);
    return 0;
}

那我们接下来看下面一道题

BJWC2012 冻结

题目描述

“我要成为魔法少女!” “那么,以灵魂为代价,你希望得到什么?” “我要将有关魔法和奇迹的一切,封印于卡片之中„„”

在这个愿望被实现以后的世界里,人们享受着魔法卡片(SpellCard,又名符卡)带来的便捷。

现在,不需要立下契约也可以使用魔法了!你还不来试一试? 比如,我们在魔法百科全书(Encyclopedia of Spells)里用“freeze”作为关键字来查询,会有很多有趣的结果。 例如,我们熟知的Cirno,她的冰冻魔法当然会有对应的 SpellCard 了。 当然,更加令人惊讶的是,居然有冻结时间的魔法,Cirno 的冻青蛙比起这些来真是小巫见大巫了。 这说明之前的世界中有很多魔法少女曾许下控制时间的愿望,比如 Akemi Homura、Sakuya Izayoi、„„ 当然,在本题中我们并不是要来研究历史的,而是研究魔法的应用。

我们考虑最简单的旅行问题吧: 现在这个大陆上有 N 个城市,M 条双向的道路。城市编号为 1~N,我们在 1 号城市,需要到 N 号城市,怎样才能最快地到达呢? 这不就是最短路问题吗?我们都知道可以用 Dijkstra、Bellman-Ford、Floyd-Warshall等算法来解决。 现在,我们一共有 K 张可以使时间变慢 50%的 SpellCard,也就是说,在通过某条路径时,我们可以选择使用一张卡片,这样,我们通过这一条道路的时间 就可以减少到原先的一半。需要注意的是:

在一条道路上最多只能使用一张 SpellCard。 使用一张SpellCard 只在一条道路上起作用。 你不必使用完所有的 SpellCard。

给定以上的信息,你的任务是:求出在可以使用这不超过 K 张时间减速的 SpellCard 之情形下,从城市1 到城市N最少需要多长时间。

输入输出格式

输入格式:

第一行包含三个整数:N、M、K。

接下来 M 行,每行包含三个整数:Ai、Bi、Timei,表示存在一条 Ai与 Bi之间的双向道路,在不使用 SpellCard 之前提下,通过它需要 Timei的时间。

输出格式:

输出一个整数,表示从1 号城市到 N号城市的最小用时。

这题看起来是一个非常有意思的题

题面肥肠长而且是一个生动的故事

但是其实就是一个普通的分层图

跟上面一道题完全是一样的

只不过就是不同的层之间 权值除以二

就行了

然后我贴一波代码

大家对照着这个代码就能理解了

const int MAXX=2147483647;
using namespace std;
struct Edge{
    int next,to,w;
}e[1000000];
int head[1000000];
int n,m,k,cnt;
int dis[1000001];
bool vis[1000001];
inline void add(int x,int y,int w){
    cnt++;
    e[cnt].next=head[x];
    e[cnt].w=w;
    e[cnt].to=y;
    head[x]=cnt;
}
inline void spfa(){
    queue<int>q;
    for(int i=1;i<=n*(k+1);i++){
        dis[i]=MAXX;
        vis[i]=false;
    }
    dis[1]=0;
    vis[1]=1;
    q.push(1);
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=e[i].next){
            int v=e[i].to;
            if(dis[v]>dis[x]+e[i].w){
                dis[v]=dis[x]+e[i].w;
                if(!vis[v]){
                    q.push(v);
                    vis[v]=true;
                }
            }
        }
        vis[x]=0;
    }
}
int main(){
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=m;i++){
        int u,v,t;
        scanf("%d%d%d",&u,&v,&t);
        for(int j=0;j<=k;j++){//分层图建图 
            add(u+j*n,v+j*n,t); 
            add(v+j*n,u+j*n,t);
        } 
        for(int j=0;j<k;j++){
            add(u+j*n,v+(j+1)*n,t>>1);
            add(v+j*n,u+(j+1)*n,t>>1);
        } 

    }
    spfa();
    int ans=MAXX;
    for(int i=0;i<=k;i++){
        ans=min(dis[n+i*n],ans);
    }
    printf("%d",ans);
    return 0;
} 

很良心呢!

这道题可以用SPFA卡过!!!

不是卡过

SPFA就是正解

所以分层图的讲解就结束了

写在后面:

分层图其实比较好理解

关键是和其他图论算法的结合

能否成功的建立模型 然后就可以进行最短路 网络流等算法

同时如果开一维数组dis

记得控制好空间!!!

不要MLE也不要RE!!!!