浅谈分层图最短路问题

分层图最短路问题,就是把一个图分层然后跑最短路(废话)。

分层图最短路问题关键在于怎么分层,分层通常是起到对题中某个条件的限定作用,这里我们结合例题看看。

Luogu P4568飞行路线

题意大致是给一个带权无向图,允许k次飞行费用为0,求最小费用。

这里就是将图分成k层,每次从第i-1层到第i层相当于是走了一条免费的飞行路线。然后如果从第i层回到第i-1层就是一个“后悔”的过程。因此建图方法就是每层之间正常连边,层与层之间上到下边权为0,下到上正常边权。这样直接跑Dijkstra就好了。这里分层就是对k次免费进行限制,而且连反向的边保证程序有反悔的机会。

搬运一个大佬题解里的图。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
#define N 1000010
#define M 5000010
using namespace std;
int head[N],nxt[M],to[M],val[M];
int n,m,cnt;
void Add(int u,int v,int w)
{
    nxt[++cnt] = head[u];
    head[u] = cnt;
    to[cnt] = v;
    val[cnt] = w;
    return;
}
int k,s,t;
int dis[N];
struct qwq
{
    int u,dis;
    bool operator < (const qwq &a) const
    {
        return dis > a.dis;
    }
}top,node;
priority_queue<qwq>q;
const int inf = 100000010;
bool vis[N];
void dijkstra()
{
    for(int i = 0;i <= n + k * n;i++) dis[i] = inf;
    dis[s] = 0;
    top.u = s;
    top.dis = 0;
    q.push(top);
    while(q.size())
    {
        top = q.top();
        q.pop();
        int u = top.u;
        if(!vis[u])
        {
            vis[u] = 1;
            for(int i = head[u];i;i = nxt[i])
            {
                int v = to[i];
                if(dis[v] > dis[u] + val[i])
                {
                    dis[v] = dis[u] + val[i];
                    node.u = v,node.dis = dis[v];
                    q.push(node);
                }
            }
        }
    }
}
int main()
{
    scanf("%d %d %d",&n,&m,&k);
    scanf("%d %d",&s,&t);
    for(int i = 1;i <= m;i++)
    {
        int u,v,w;
        scanf("%d %d %d",&u,&v,&w);
        Add(u,v,w);
        Add(v,u,w);
        for(int i = 1;i <= k;i++)
        {
            Add(u + (i - 1) * n,v + i * n,0);
            Add(v + i * n,u + i * n,w);
            Add(v + (i - 1) * n,u + i * n,0);
            Add(u + i * n,v + i * n,w);
        }
    }
    for(int j = 1;j <= k;j++)
    {
        Add(t + (j - 1) * n,t + j * n,0);
    }
    dijkstra();
    printf("%d",dis[t + k * n]);
        return 0;  
}
P4568

Luogu P1073最优贸易

题意要求我们从一个点买入,一个点卖出,获得的费用最大,并且从1能到达n。

emmmm这个题题解里有很多玄学做法,这里只讨论分层图做法。我们暴力的想一想,每一个点都有可能买入,每一个点都有可能卖出,但是必须先买入再卖出(显然的嘛)。所以这其实就存在一个依赖关系。这样就可以将图分成3层,第一层表示没有买入和卖出,第二层表示已经买入,第三层表示已经卖出。那么每层内部正常连边(边权为0),层与层之间,第一层到第二层要连边权为-a[u](u为第一层内边的起点,a[u]为费用)表示买入,第二层到第三层连边权为a[u]的边表示卖出。而第二层第三层回不到第一层就是我们限制了贸易次数,只进行一次贸易。这样图就建好了。

仍然搬运大佬题解的图。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<ctime>
#include<cstdlib>
#include<set>
#include<queue>
#include<vector>
#include<string>
using namespace std;

#define P system("pause");
#define A(x) cout << #x << " " << (x) << endl;
#define AA(x,y) cout << #x << " " << (x) << " " << #y << " " << (y) << endl;
#define ll long long
#define inf 1000000000
#define linf 10000000000000000
#define mem(x) memset(x,0,sizeof(x))

int read()
{
    int x = 0,f = 1;
    char c = getchar();
    while(c < '0' || c > '9')
    {
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9')
    {
        x = (x << 3) + (x << 1) + c - '0';
        c = getchar();
    }
    return f * x;
}
#define N 300100
#define M 3000010
int head[N],nxt[M],to[M],val[M],dis[N],vis[N],a[N];
int n,m,cnt;
void add(int u,int v,int w)
{
    nxt[++cnt] = head[u];
    head[u] = cnt;
    to[cnt] = v;
    val[cnt] = w;
    return;
}
void Add(int u,int v)
{
    add(u,v,0);
    add(u + n,v + n,0);
    add(u + 2 * n,v + 2 * n,0);
    add(u,v + n,-a[u]);
    add(u + n,v + 2 * n,a[u]);
}
int s,t;
void spfa()
{
    queue<int>q;
    q.push(s);
    for(int i = 1;i <= t;i++)
    {
        dis[i] = -inf;
    }
    vis[s] = 1;
    dis[s] = 0;
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        vis[u] = 0;
        for(int i = head[u];i;i = nxt[i])
        {
            int v = to[i];
            if(dis[v] < dis[u] + val[i])
            {
                dis[v] = dis[u] + val[i];
                if(!vis[v])
                {
                    vis[v] = 1;
                    q.push(v);
                }
            }
        }
    }
    return;
}
int main()
{
    n = read(),m = read();
    s = 1,t = 3 * n + 1;
    for(int i = 1;i <= n;i++) a[i] = read();
    for(int i = 1;i <= m;i++)
    {
        int u = read(),v = read(),z = read();
        Add(u,v);
        if(z == 2) Add(v,u);
    }
    add(n,t,0);
    add(3 * n,t,0);
    spfa();
    printf("%d
",dis[t]);
}
P1073

总结一下,分层图最短路通常是按照题中的限制关系进行分层,通过层与层间的连通与否,边权进行限制,从而实现在跑最短路时也能兼顾限制条件。同时由于最短路算法是枚举点进行松弛,无法保证一次就得到最优解,因此往往通过一些方法实现“反悔”操作,例题1就是通过回到原来的层实现的,而例题2则是通过spfa不断松弛实现的。

原文地址:https://www.cnblogs.com/lijilai-oi/p/11285708.html