载谭费用流对偶

一些你可能需要的前置芝士:

线性规划问题 线性规划问题是如下的问题:

(n)非负变量 (x),以及 (m) 个约束,形如:

[sum_j a_{i,j}x_jleq b_i ]

要求最大化 (sum_i c_ix_i) 的值,记做:

[max c^{mathsf T} x\ s.t.\ Axleq b\ xgeq 0\ ]

一个线性规划的对偶问题是如下的问题:

(m)非负变量 (y),以及 (n) 个约束,形如:

[sum_i a_{i,j}y_igeq c_j ]

要求最小化 (sum_i b_iy_i) 的值,记做:

[min b^{mathsf T} y\ s.t.\ A^{mathsf T}ygeq c\ ygeq 0\ ]

关于线性规划问题,我们有如下的定理:

弱对偶定理 线性规划问题的解总是小于等于其对偶问题的解。

证明

[egin{aligned} & sum_i c_ix_i\ leq & sum_i x_isum_j a_{j,i}y_j\ = & sum_j y_jsum_i a_{i,j}x_i\ leq & sum_j y_jb_j end{aligned} ]

值得注意的是,弱对偶定理对所有的规划问题都成立,包括但不限于整数规划等。

强对偶定理 线性规划的解等于其对偶问题的解。

这个定理证明较为复杂,这里略去。

不同于弱对偶定理,强对偶定理只对线性规划成立。


我们先来考虑一个线性规划的扩展形式:如何对 (sum_ia_ix_i=b_i) 的条件做对偶?

解决方案是,我们把它拆成如下两个条件:

[sum_ia_ix_ileq b_i\ sum_i-a_ix_ileq -b_i ]

然后记上面那个条件对偶后的变量为 (y_1),下面的为 (y_2),那么最后要最小化的就是 (b_i(y_1-y_2)),于是我们记 (varphi_i=y_1-y_2),此时 (varphi_i) 就没有 (geq 0) 的限制了。

现在来考虑最大费用循环流的线性对偶形式:

[max sum w_{u,v}f_{u,v}\ s.t.\ 0leq f_{u,v}leq c_{u,v}\ sum_{v} f_{u,v}-f_{v,u}=0 ]

于是直接对偶问题可得:

[min sum c_{u,v}x_{u,v}\ s.t.\ x_{u,v}geq 0\ varphi_u-varphi_v+x_{u,v}geq w_{u,v} ]

注意到这个形式下,(x_{u,v}) 可以直接取到最小值,于是就等价于

[min sum c_{u,v}max(0,w_{u,v}-varphi_u+varphi_v) ]

于是所有这种形式的问题都可以用最大费用循环流建图。


下面是例题。

Problem(「ZJOI2013」防守战线).

长为 (n) 的序列,初始全为 (0),在 (i) 处加 (1)(c_i) 的代价。有 (m) 个限制,形如 ([l_i,r_i]) 的区间和必须 (geq d_i)。求最小代价。

(nleq 1000,mleq 10000)

sol.

记前缀和为 (S_i),于是总的代价为

[sum_i c_imax(S_i-S_{i-1},0)+sum_iinftymax(S_{l_i-1}-S_{r_i}+d_i,0) ]

直接拿着跑最大费用循环流即可。可能需要注意一下建图的方式,根据实际测试,不同的建图方式速度差异足足有 (30) 倍。

#include <bits/stdc++.h>

#define nya(neko...) fprintf(stderr, neko)

constexpr int INF = 0x3f3f3f3f;
constexpr int maxn = 1005;
constexpr int maxm = 10005;

int n, m;
namespace Flow {
	int s, t, tot = 1, minCost, maxFlow;
	int first[maxn], cur[maxn];
	struct Edge { int to, nxt, cap, w; } e[maxn + maxm << 2];
	
	inline void Add(int u, int v, int cap, int w) {
		e[++tot] = { v, first[u], cap, w };
		first[u] = tot;
	}
	inline void Adde(int u, int v, int cap, int w) {
		Add(u, v, cap, w), Add(v, u, 0, -w);
	}
	
	int phi[maxn]; bool inque[maxn];
	inline void SPFA() {
		memset(phi, INF, sizeof phi);
		std::queue<int> q;
		
		phi[s] = 0, q.push(s), inque[s] = 1;
		while(!q.empty()) {
			int u = q.front(); inque[u] = 0, q.pop();
			for(int i = first[u]; i; i = e[i].nxt) {
				int v = e[i].to;
				if(e[i].cap && phi[v] > phi[u] + e[i].w) {
					phi[v] = phi[u] + e[i].w;
					if(!inque[v]) inque[v] = 1, q.push(v);
				}
			}
		}
	}
	
	inline int cost(int id) { // reduced cost
		return phi[e[id ^ 1].to] - phi[e[id].to] + e[id].w;
	}
	
	int dis[maxn];
	inline bool dij() {
		memset(dis, INF, sizeof dis);
		
		using node = std::pair<int, int>;
		std::priority_queue<node, std::vector<node>, std::greater<node>> q;
		
		q.emplace(dis[s] = 0, s);
		while(!q.empty()) {
			auto o = q.top(); q.pop();
			
			int u = o.second;
			if(dis[u] != o.first) continue;
			for(int i = first[u]; i; i = e[i].nxt) {
				int v = e[i].to;
				if(e[i].cap && dis[v] > dis[u] + cost(i)) {
					dis[v] = dis[u] + cost(i);
					q.emplace(dis[v], v);
				}
			}
		}
		return dis[t] < INF;
    }
	
	bool vis[maxn];
	inline int DFS(int u, int flow) {
		if(u == t) return flow;
		
		vis[u] = 1;

		int res = 0;
		for(int &i = cur[u]; i; i = e[i].nxt) {
			int v = e[i].to;
			if(vis[v] || !e[i].cap || dis[v] != dis[u] + cost(i)) continue;
			
			int f = DFS(v, std::min(flow, e[i].cap));
			
			e[i].cap -= f, e[i ^ 1].cap += f;
			flow -= f, res += f;
			minCost += f * e[i].w;
			
			if(!flow) break;
		}
		vis[u] = 0;
		if(flow) vis[u] = 1;
		return res;
	}
	
	inline void Dinic() {
		SPFA();
		
		while(dij()) {
			memcpy(cur, first, sizeof cur);
			memset(vis, false, sizeof vis);
			while(int x = DFS(s, INF)) maxFlow += x;
			
			for(int i = 0; i <= n; ++i) // all nodes
				if(dis[i] < INF) phi[i] += dis[i];
		}
	}
}
using Flow::s;
using Flow::t;
using Flow::Adde;

int deg[maxn];
int main() {
	scanf("%d%d", &n, &m), s = n + 1, t = n + 2;
	for(int i = 1, c; i <= n; ++i) {
		scanf("%d", &c), Adde(i, i - 1, INF, 0);
		deg[i] += c, deg[i - 1] -= c;
	}
	
	for(int i = 1, l, r, d; i <= m; ++i) {
		scanf("%d%d%d", &l, &r, &d);
		Adde(r, l - 1, INF, -d);
	}
	
	for(int i = 0; i <= n; ++i) {
		if(deg[i] >= 0) Adde(s, i, deg[i], 0);
		else Adde(i, t, -deg[i], 0);
	}
	Flow::Dinic(), printf("%d
", -Flow::minCost);
}

Problem(CF1307G).

(n) 个点的带权有向图。(q) 组询问,每次给出一个 (X),你可以增加每条边的边权,要求总的变化量不超过 (X),求 (1)(n) 的最短路的最大值。

(nleq 50,qleq 10^5)

sol.

考虑最大费用循环流的一个特化:有源汇最大费用可行流,其形式如下:

[maxsum w_{u,v}f_{u,v}\ s.t.\ 0leq f_{u,v}leq c_{u,v}\ sum_{u,v}f_{u,v}-f_{v,u}= left{ egin{aligned} 0 & & (u eq s,u eq t)\ F & & (u=s)\ -F & & (u=t)\ end{aligned} ight. ]

其对偶形式即为:

[min F(varphi_s-varphi_t)+sum c_{u,v}x_{u,v}\ s.t.\ x_{u,v}geq 0\ x_{u,v}+varphi_u-varphi_vgeq w_{u,v}\ ]

这道题中,我们考虑最小费用可行流,这就是

[Flow(s,t,F)=max F(varphi_t-varphi_s)-sum c_{u,v}x_{u,v}\ s.t.\ x_{u,v}geq 0\ varphi_v-varphi_uleq w_{u,v}+x_{u,v}\ ]

这个形式非常像题目的形式。我们令 (s=1,t=n),并定义 (varphi_1)(0),其他点的 (varphi_u)(1) 号点到它的最短路 (dis_{1,u}),于是 (x_{u,v}) 就是我们题目中要求的变化量。然后令 (c_{u,v}=1) 就有:

[Flow(1,n,F)=Fcdot dis_{1,n}-X\ dis_{1,n}leq frac{Flow(1,n,F)+X}{F} ]

于是我们就有:

[dis_{1,n}=min_F frac{Flow(1,n,F)+X}{F} ]

然后注意到 (Fleq m),我们预先处理出每个 (F) 的结果,然后不难注意到 (Flow(s,t,F)) 关于 (F) 是凸的,于是可以三分出答案。

#include <queue>
#include <cstdio>
#include <cstring>

constexpr int maxn = 55;
constexpr int maxm = maxn * maxn;
constexpr int INF = 0x3f3f3f3f;

int n, m, tot = 1, first[maxn];
struct Edge { int to, nxt, cap, w; } e[maxm << 1];

inline void Add(int u, int v, int cap, int w) {
	e[++tot] = { v, first[u], cap, w };
	first[u] = tot;
}
inline void Adde(int u, int v, int cap, int w) {
	Add(u, v, cap, w), Add(v, u, 0, -w);
}

int dis[maxn], pre[maxn];
inline bool SPFA() {
	static bool inque[maxn];
	static std::queue<int> q;

	memset(dis, INF, sizeof dis);

	q.push(1), dis[1] = 0, inque[1] = 1;
	while(!q.empty()) {
		int u = q.front(); q.pop(), inque[u] = 0;
		for(int i = first[u]; i; i = e[i].nxt) {
			int v = e[i].to;
			if(e[i].cap && dis[v] > dis[u] + e[i].w) {
				dis[v] = dis[u] + e[i].w, pre[v] = i;
				if(!inque[v]) q.push(v), inque[v] = 1;
			}
		}
	}
	return dis[n] < INF;
}

int q, R, Flow[maxm];
int main() {
	scanf("%d%d", &n, &m);
	for(int i = 1, u, v, w; i <= m; ++i) {
		scanf("%d%d%d", &u, &v, &w);
		Adde(u, v, 1, w);
	}

	R = 0;
	while(SPFA()) {
		++R, Flow[R] = Flow[R - 1] + dis[n];
		for(int u = n; u != 1; u = e[pre[u] ^ 1].to)
			--e[pre[u]].cap, ++e[pre[u] ^ 1].cap;
	}

	scanf("%d", &q);
	for(int X; q --> 0;) {
		scanf("%d", &X);

		static auto calc = [](int mid, int X) {
			return 1. * (Flow[mid] + X) / mid;
		};

		int l = 1, r = R;
		while(l < r) {
			int mid = l + r >> 1;
			if(calc(mid, X) > calc(mid + 1, X)) l = mid + 1;
			else r = mid;	
		}
		printf("%.8lf
", calc(l, X));
	}
}
原文地址:https://www.cnblogs.com/whx1003/p/15512684.html