载谭费用流对偶

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

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

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

\[\sum_j a_{i,j}x_j\leq b_i \]

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

\[\max c^{\mathsf T} x\\ s.t.\\ Ax\leq b\\ x\geq 0\\ \]

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

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

\[\sum_i a_{i,j}y_i\geq c_j \]

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

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

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

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

证明

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

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

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

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

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


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

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

\[\sum_ia_ix_i\leq b_i\\ \sum_i-a_ix_i\leq -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.\\ 0\leq 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\)。求最小代价。

\(n\leq 1000,m\leq 10000\)

sol.

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

\[\sum_i c_i\max(S_i-S_{i-1},0)+\sum_i\infty\max(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\n", -Flow::minCost);
}

Problem(CF1307G).

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

\(n\leq 50,q\leq 10^5\)

sol.

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

\[\max\sum w_{u,v}f_{u,v}\\ s.t.\\ 0\leq f_{u,v}\leq c_{u,v}\\ \sum_{u,v}f_{u,v}-f_{v,u}= \left\{ \begin{aligned} 0 & & (u\neq s,u\neq t)\\ F & & (u=s)\\ -F & & (u=t)\\ \end{aligned} \right. \]

其对偶形式即为:

\[\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_v\geq 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_u\leq 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)=F\cdot 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} \]

然后注意到 \(F\leq 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\n", calc(l, X));
	}
}
posted @ 2021-11-05 12:38  whx1003  阅读(299)  评论(0编辑  收藏  举报