『学习笔记』分层图

分层图,顾名思义,就是将图分成多层。

若要求解一个最短路问题,但是可以将其中 \(k\) 条边的权值改为 \(0\),那么就是使用分层图的时候了。对于一些其它类似的网络流问题,有涉及到改边权的,也可以用到。

为方便描述,上面的改权值操作下文中称为 "优惠"。

分层图的步骤

  1. \(k+1\) 层图,从 \(0\) 开始编号,每层图与原图一样。其中第 \(i\) 层图是用了 \(i\) 次优惠到达的图。

  2. 对于任意一条边从 \(u\) 连向 \(v\),若其在第 \(n\) 层图中,那么就新连一条边从 \(u\) 指向第 \(n+1\) 层图的 \(v\),权值为优惠的值。例如,上面的问题是改为 \(0\),那么这一条新边的权值就为 \(0\)。需要将所有这样的边都进行处理。

  3. 从第 \(0\) 层图的起始顶点开始跑单源最短路,跑完后即可按需求取答案。例如要求用完所有优惠券,那么就要输出第 \(k\) 层的终点与第 \(0\) 层的起点的最短路了。

原理

来看看两层图中间连的边:只要走了,到达下一层图了,就回不来了,即使图中存在环也是。

跑最短路过程中,要从第 \(0\) 层图跑到第 \(k\) 层图。

而且走了优惠边后去到的那个顶点是下一层图的 \(v\),和直接走原边到达的顶点可以说是一样的。只不过用了一次优惠。

所以结论就是:到达第 \(n\) 层图后,就说明一定用了 \(k\) 次优惠。

如果在任意一层图上到达了终点,那么也可以尝试更新答案。

P4568 [JLOI2011]飞行路线

题目大意

给定一张无向带权图,其中有 \(k\) 次机会可以将一条边的权值变为 \(0\),求最短路。

思路

板子一道。

对于建边,我们可以在输入时进行。

先按正常情况建双向边,再从 \(1\) 循环到 \(k\),每次将第 \(i\) 层图的边建立起来,并连两条从第 \(i-1\) 层到第 \(i\) 层,权值为 \(0\),一条从第 \(i-1\) 层的 \(u\) 连向第 \(i\) 层的 \(v\),另一条则相反,\(i-1\) 层的 \(v\) 连向 \(i\) 层的 \(u\)

还要注意一下数据范围。

\(2 \leq n \leq 10^4\)\(1 \leq m \leq 5 \times 10^4\)\(0 \leq k \leq 10\)

那么,要开的顶点数和边数就是:

\(\texttt{MAXN}=nk=10^4 \times 10=10^5\)\(\texttt{MAXM}=2mk=2 \times 5 \times 10^4 \times 10=5 \times 10^5 \times 2=10^6\)

这个 \(k\) 不知道咋回事,要两倍才行。还有这个 \(m\) 也是,要多开一点。\(\texttt{MAXM}\) 我开到了 \(5 \times 10^6\) 才行。

代码

#include <iostream>
#include <queue>
using namespace std;
template<typename T=int>
inline T read(){
    T X=0; bool flag=1; char ch=getchar();
    while(ch<'0' || ch>'9'){if(ch=='-') flag=0; ch=getchar();}
    while(ch>='0' && ch<='9') X=(X<<1)+(X<<3)+ch-'0',ch=getchar();
    if(flag) return X;
    return ~(X-1);
}

template<typename T=int>
inline void write(T X){
    if(X<0) putchar('-'),X=~(X-1);
    T s[20],top=0;
    while(X) s[++top]=X%10,X/=10;
    if(!top) s[++top]=0;
    while(top) putchar(s[top--]+'0');
    putchar('\n');
}

const int N=1e6+5,M=5e6+5,inf=0x3f3f3f3f;
struct edge{
    int to,nxt,val;
}e[M]; // 链式前向星用
int n,m,k,s,t,u,v,w,z,ans=inf; // n,m,k,s,t 题目给出,u,v,w 临时村边,z 临时变量,ans 计算答案用
int head[N],top; // 链式前向星用
int dist[N],vis[N]; // dij 用,分别记录起始顶点到各个顶点的距离,是否访问过
priority_queue<pair<int,int>> q; // dij 用,优先队列

void add(int u,int v,int w){
    top++;
    e[top].to=v;
    e[top].val=w;
    e[top].nxt=head[u];
    head[u]=top;
}

void dijkstra(){ // dij 板子
    for(int i=0,l=n*k+n; i<=l; i++) // 注意初始化的范围,是从 0 到第 k 层图的最后一个顶点
        dist[i]=inf;
    dist[s]=0;
    q.push(make_pair(0,s));
    while(!q.empty()){
        u=q.top().second;
        q.pop();
        if(vis[u]) continue;
        vis[u]=1;
        for(int i=head[u]; i; i=e[i].nxt){
            v=e[i].to;
            if(dist[v]>dist[u]+e[i].val){
                dist[v]=dist[u]+e[i].val;
                q.push(make_pair(-dist[v],v));
            }
        }
    }
}

int main(){
    n=read(),m=read(),k=read(),s=read(),t=read();
    while(m--){
        u=read(),v=read(),w=read();
        add(u,v,w);
        add(v,u,w); // 原图建边
        for(int i=1; i<=k; i++){ // 然后再建 1~k 层图的边
            z=i*n; // 很多重复运算,所以用 z 代替。表示第 i 层图的起始下标
            add(u+z,v+z,w);
            add(v+z,u+z,w); // 复制上面的边到每层图中
            add(u+z-n,v+z,0);
            add(v+z-n,u+z,0); // 建立优惠边
        }
    }
    dijkstra();
    for(int i=0; i<=k; i++)
        ans=min(ans,dist[t+n*i]);
        // 由于最短路的终点可能是任意一层的终点
        // 所以需要取所有终点的最小值
    write(ans);
    return 0;
}

\(n\) 倍经验

按照个人认为的难度排序。前两道就是板子,后面两道就有点难度了。

posted @ 2022-02-16 20:20  仙山有茗  阅读(199)  评论(0编辑  收藏  举报