浅谈分层图最短路问题


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

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

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\n",dis[t]); }
P1073

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


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM