「学习笔记」最小费用流最大流_势函数+dij

本文探讨了如何使用势能函数(h)处理负权边,通过构造hhh数组和SPFA的巧妙调整,避免了SPFA中大规模计算的问题,实现了在有负权边的图中寻找最短路径。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、背景

dijkstradijkstradijkstra 不能处理负边权,比如这个图就会挂掉。

在这里插入图片描述

它的过程是 1→2,1→31 \rightarrow 2, 1 \rightarrow 312,13,然后就结束了。

为了处理有负权的图,我们要想一个优秀的东西去解决它。

所以在这里引入一个 NBNBNB 的东西: 势能函数 (h)(h)(h)


二、思路

先不考虑实现,我们通过一些 NBNBNB 的操作,将 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→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]\begin{aligned} s \leadsto t &= s \rightarrow v_1 + v_1 \rightarrow v_2 + ... + v_n \rightarrow 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] \end{aligned}st=sv1+v1v2+...+vnt=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⇝ts \leadsto tst 被影响的值都是 h[s]−h[t]h[s] - h[t]h[s]h[t],所以修改图上的最短路等于原图上的最短路。

现在考虑构造一个 hhh 数组。

三、实现

容易想到 ∀i∈E,w[si][ti]+h[si]−h[ti]≥0\forall i \in \mathbb{E}, w[s_i][t_i] + h[s_i] - h[t_i] \geq 0iE,w[si][ti]+h[si]h[ti]0h[si]+w[si][ti]≥h[ti]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],所以跑一个最短路就能求出当前图的一个满足要求的 hhh,这时由于有负边,所以要用 spfaspfaspfa

可是我们就是要摆脱 SPFASPFASPFA 上界为 nmnmnm 的梦魇,现在不是又回来了吗?所以我们要利用上一次的 hhh 调整下一次的 hhh

由于这玩意就只是一个单纯的构造,这里直接给出一种构造方法。

在当前的修改图上跑一个 dist[u]dist[u]dist[u] ,记录 s⇝us \leadsto usu 的最短路,下一个图的 hhhh[u]+dist[u]h[u] + dist[u]h[u]+dist[u]


正确性:

根据三的结论(即差分约束正确性),我们可以知道:在图不变的情况下,这样的 hhh 一定是对的,但是由于增广,会有一些反向边被搞进来。

在这里插入图片描述

如上图,假设 s→v→ts \rightarrow v \rightarrow tsvt 为最短路径 (l)(l)(l),那么可能会使 hhh 有问题的边一定是 lll 上的反向边。

因为是最短路,所以 ∀i∈L,h[ti]=h[si]+w[si][ti]\forall i \in \mathbb{L}, h[t_i] = h[s_i] + w[s_i][t_i]iL,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[ti]=h[si]−w[ti][si]⇒h[si]=h[ti]+w[ti][si]\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;

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值