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

一、背景

d i j k s t r a dijkstra dijkstra 不能处理负边权,比如这个图就会挂掉。

在这里插入图片描述

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

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

所以在这里引入一个 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 ] \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 ⇝ t s \leadsto t st 被影响的值都是 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 iE,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 su 的最短路,下一个图的 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 svt 为最短路径 ( 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] 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 [ 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;

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值