「学习笔记」最小费用流最大流_势函数+dij
一、背景
d i j k s t r a dijkstra dijkstra 不能处理负边权,比如这个图就会挂掉。
它的过程是 1 → 2 , 1 → 3 1 \rightarrow 2, 1 \rightarrow 3 1→2,1→3,然后就结束了。
为了处理有负权的图,我们要想一个优秀的东西去解决它。
所以在这里引入一个 N B NB NB 的东西: 势能函数 ( h ) (h) (h)。
二、思路
先不考虑实现,我们通过一些 N B NB NB 的操作,将 w [ i ] [ j ] w[i][j] w[i][j] 全部改为 w [ i ] [ j ] + h [ i ] − h [ j ] w[i][j] + h[i] - h[j] w[i][j]+h[i]−h[j] 并保证其为非负数。这时
s ⇝ t = s → v 1 + v 1 → v 2 + . . . + v n → t = w [ s ] [ v 1 ] + h [ s ] − h [ v 1 ] + w [ v 1 ] [ v 2 ] + h [ v 1 ] − h [ v 2 ] + . . . + w [ v n ] [ t ] + h [ v n ] − h [ t ] = w [ s ] [ v 1 ] + w [ v 1 ] [ v 2 ] + . . . + w [ v n ] [ t ] + h [ s ] − h [ t ] s⇝t=s→v1+v1→v2+...+vn→t=w[s][v1]+h[s]−h[v1]+w[v1][v2]+h[v1]−h[v2]+...+w[vn][t]+h[vn]−h[t]=w[s][v1]+w[v1][v2]+...+w[vn][t]+h[s]−h[t]
所以任意一条 s ⇝ t s \leadsto t s⇝t 被影响的值都是 h [ s ] − h [ t ] h[s] - h[t] h[s]−h[t],所以修改图上的最短路等于原图上的最短路。
现在考虑构造一个 h h h 数组。
三、实现
容易想到 ∀ i ∈ E , w [ s i ] [ t i ] + h [ s i ] − h [ t i ] ≥ 0 \forall i \in \mathbb{E}, w[s_i][t_i] + h[s_i] - h[t_i] \geq 0 ∀i∈E,w[si][ti]+h[si]−h[ti]≥0 即 h [ s i ] + w [ s i ] [ t i ] ≥ h [ t i ] h[s_i] + w[s_i][t_i] \geq h[t_i] h[si]+w[si][ti]≥h[ti],一个明显的差分约束是不是? h [ t ] h[t] h[t] 的上界是最小的 h [ s ] + w [ s ] [ t ] h[s] + w[s][t] h[s]+w[s][t],所以跑一个最短路就能求出当前图的一个满足要求的 h h h,这时由于有负边,所以要用 s p f a spfa spfa。
可是我们就是要摆脱 S P F A SPFA SPFA 上界为 n m nm nm 的梦魇,现在不是又回来了吗?所以我们要利用上一次的 h h h 调整下一次的 h h h。
由于这玩意就只是一个单纯的构造,这里直接给出一种构造方法。
在当前的修改图上跑一个 d i s t [ u ] dist[u] dist[u] ,记录 s ⇝ u s \leadsto u s⇝u 的最短路,下一个图的 h h h 为 h [ u ] + d i s t [ u ] h[u] + dist[u] h[u]+dist[u]。
正确性:
根据三的结论(即差分约束正确性),我们可以知道:在图不变的情况下,这样的 h h h 一定是对的,但是由于增广,会有一些反向边被搞进来。
如上图,假设 s → v → t s \rightarrow v \rightarrow t s→v→t 为最短路径 ( l ) (l) (l),那么可能会使 h h h 有问题的边一定是 l l l 上的反向边。
因为是最短路,所以 ∀ i ∈ L , h [ t i ] = h [ s i ] + w [ s i ] [ t i ] \forall i \in \mathbb{L}, h[t_i] = h[s_i] + w[s_i][t_i] ∀i∈L,h[ti]=h[si]+w[si][ti]
又 ∵ w [ x ] [ y ] = − w [ y ] [ x ] \because w[x][y] = -w[y][x] ∵w[x][y]=−w[y][x]
∴ h [ t i ] = h [ s i ] − w [ t i ] [ s i ] ⇒ h [ s i ] = h [ t i ] + w [ t i ] [ s i ] \therefore h[t_i] = h[s_i] - w[t_i][s_i] \Rightarrow h[s_i] = h[t_i] + w[t_i][s_i] ∴h[ti]=h[si]−w[ti][si]⇒h[si]=h[ti]+w[ti][si]
所以也满足要求。
四、参考代码
const int Maxn = 1e5;
const int Maxm = 1e7;
const LL Limit = 1e14;
const LL Inf = 0x3f3f3f3f3f3f3f;
struct Date {
int x, y; LL flux, val;
Date () {}
Date (int _x, int _y, LL _flux, LL _val) {
x = _x; y = _y; flux = _flux; val = _val;
}
};//存储边
struct edge {
int to[Maxm * 2 + 5], Next[Maxm * 2 + 5]; LL flux[Maxm * 2 + 5], val[Maxm * 2 + 5];
int len, Head[Maxn + 5];
edge () { len = 1; memset (Head, 0, sizeof Head); }
void Init () { len = 1; memset (Head, 0, sizeof Head); }
void plus (int x, int y, LL _flux, LL _val) {
to[++len] = y;
flux[len] = _flux;
val[len] = _val;
Next[len] = Head[x];
Head[x] = len;
}
void add (int x, int y, LL _flux, LL _val) {
plus (x, y, _flux, _val);
plus (y, x, 0, -_val);
}
void rev_add (int x, int y, LL _flux, LL _val) {
plus (x, y, 0, _val);
plus (y, x, _flux, -_val);
}
};//链式前向星
struct Max_Flow {
edge mp;
Date e[Maxm + 5];
int n, m, s, t;
bool vis[Maxn + 5];
int hh, tt, q[Maxn + 5];
LL dist[Maxn + 5];
int fa[Maxn + 5];//记录来边的编号
void Init () {//初始化
mp.Init ();
hh = 1; tt = 0;
n = m = s = t = 0;
memset (vis, 0, sizeof vis);
memset (dist, 0x3f, sizeof dist);
}
LL Update () {//修改增广路上的边
int p = t; LL cost = 0, flow = Inf;
while (p != s) {
cost += mp.val[fa[p]];
flow = Min (flow, mp.flux[fa[p]]);
p = mp.to[fa[p] ^ 1];
}
p = t;
while (p != s) {
mp.flux[fa[p]] -= flow;
mp.flux[fa[p] ^ 1] += flow;
p = mp.to[fa[p] ^ 1];
}
return cost * flow;
}
void Build_Positive () {//建图
mp.Init ();
rep (i, 1, m)
mp.add (e[i].x, e[i].y, e[i].flux, e[i].val);
}
LL h[Maxn + 5];
void Spfa_For_Dijkstra () {//求出初始 h (spfa 模板)
memset (vis, 0, sizeof vis);
memset (dist, 0x3f, sizeof dist);
hh = 1; tt = 0; q[++tt] = s; dist[s] = 0;
while (hh <= tt) {
int u = q[hh++];
vis[u] = 0;
for (int i = mp.Head[u]; i; i = mp.Next[i]) {
int v = mp.to[i]; LL flux = mp.flux[i], w = mp.val[i];
if (flux == 0) continue;
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
if (!vis[v]) {
q[++tt] = v;
vis[v] = 1;
}
}
}
}
rep (i, 1, n)
if (h[i] + dist[i] < Limit)
h[i] += dist[i];
}
bool Dijkstra () {//求出新的 h 和增广路(dijkstra 模板)
memset (vis, 0, sizeof vis);
memset (dist, 0x3f, sizeof dist);
priority_queue <PII, vector <PII>, greater <PII> > p;
p.push (MP (0, s)); dist[s] = 0;
while (p.size ()) {
PII tmp = p.top (); p.pop ();
int u = tmp.se;
if (vis[u]) continue; vis[u] = 1;
for (int i = mp.Head[u]; i; i = mp.Next[i]) {
int v = mp.to[i]; LL flux = mp.flux[i], w = mp.val[i] + h[u] - h[v];
if (flux == 0) continue;
if (vis[v]) continue;
if (dist[u] + w < dist[v]) {
dist[v] = dist[u] + w;
fa[v] = i;
p.push (MP (dist[v], v));
}
}
}
rep (i, 1, n)//修改 h
if (h[i] + dist[i] < Limit)
h[i] += dist[i];
return dist[t] <= Limit;
}
LL Cost_Positive_Dijkstra () {
Build_Positive ();
Spfa_For_Dijkstra ();
LL res = 0;
while (Dijkstra ()) {//一直找到没有增广路
res += Update ();
}
return res;
}
}G;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通