Cost flow duality

Keywords: Graph Theory

Some pre cheese you may need:

Linear programming problem linear programming problem is the following problem:

There are \ (n \) non negative variables \ (x \) and \ (m \) constraints, such as:

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

It is required to maximize the value of \ (\ sum_i c_ix_i \), as follows:

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

The dual problem of a linear programming is the following problem:

There are \ (m \) non negative variables \ (y \) and \ (n \) constraints, such as:

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

Minimize the value of \ (\ sum_i b_iy_i \), as follows:

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

For linear programming, we have the following theorem:

Weak duality theorem the solution of linear programming problem is always less than or equal to the solution of its dual problem.

prove

\[\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} \]

It is worth noting that the weak duality theorem holds for all programming problems, including but not limited to integer programming.

Strong duality theorem the solution of linear programming is equal to the solution of its dual problem.

The proof of this theorem is more complex and is omitted here.

Unlike the weak duality theorem, the strong duality theorem holds only for linear programming.

Let's consider an extended form of linear programming: how to dual the condition of \ (\ sum_ia_ix_i=b_i \)?

The solution is to split it into the following two conditions:

\[\sum_ia_ix_i\leq b_i\\ \sum_i-a_ix_i\leq -b_i \]

Then remember that the variable after the above condition dual is \ (y_1 \) and the following is \ (y_2 \), so the last thing to minimize is \ (b_i(y_1-y_2) \), so we remember \ (\ varphi_i=y_1-y_2 \), and at this time \ (\ varphi_i \) has no limit of \ (\ geq 0 \).

Now consider the linear dual form of the maximum cost cyclic flow:

\[\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 \]

Therefore, the direct dual problem can be obtained:

\[\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} \]

Note that in this form, \ (x_{u,v} \) can directly take the minimum value, so it is equivalent to

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

Therefore, all this form of problems can be mapped with the maximum cost cycle flow.

The following is an example.

Problem ("ZJOI2013" defensive front)

The sequence with length of \ (n \) is initially all \ (0 \), and adding \ (1 \) at \ (I \) has the cost of \ (c_i \). There are \ (m \) restrictions, such as the interval of \ ([l_i,r_i] \) and must be \ (\ geq d_i \). Find the minimum cost.

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

sol.

Remember that the prefix sum is \ (S_i \), so the total cost is

\[\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) \]

Just hold the maximum cost circulation flow directly. You may need to pay attention to the way of drawing. According to the actual test, the speed difference of different drawing methods is \ (30 \) times.

#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).

\Weighted digraph of (n \) points\ (q \) group query, give one \ (X \) at a time, you can increase the edge weight of each edge, require the total change not to exceed \ (X \), and find the maximum value of the shortest circuit from \ (1 \) to \ (n \).

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

sol.

Consider a specialization of the maximum cost cycle flow: the maximum cost feasible flow with source and sink, which is in the form of:

\[\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. \]

Its dual form is:

\[\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}\\ \]

In this problem, we consider the minimum cost feasible flow, which is

\[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}\\ \]

This form is very similar to the form of the topic. We make \ (s=1,t=n \), and define \ (\ varphi_1 \) as \ (0 \), and \ (\ varphi_ \) of other points is the shortest path \ (dis_{1,u} \) from point \ (1 \), so \ (x_{u,v} \) is the change required in our topic. Then let \ (c_{u,v}=1 \) have:

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

So we have:

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

Then notice that \ (F\leq m \), we preprocess the results of each \ (F \), and then it is not difficult to notice that \ (Flow(s,t,F) \) is convex about \ (F \), so we can divide the answers in three.

#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 by BZorch on Thu, 04 Nov 2021 20:52:28 -0700