• 载谭费用流对偶


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

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

    (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));
    	}
    }
    
  • 相关阅读:
    php如何导出csv文件(代码示例)
    【转】Linux 进程终止后自动重启
    【转】小程序web-view覆盖原生组件
    搭建 LNMP 环境
    数据库索引的底层原理
    NoSQL 介绍
    MySQL Explain详解
    centos搭建 SVN 服务器
    【转】提高mysql千万级大数据SQL查询优化30条经验(Mysql索引优化注意)
    优化mysql slave的同步速度
  • 原文地址:https://www.cnblogs.com/whx1003/p/15512684.html
Copyright © 2020-2023  润新知