一些你可能需要的前置芝士:
线性规划问题 线性规划问题是如下的问题:
有 (n) 个非负变量 (x),以及 (m) 个约束,形如:
要求最大化 (sum_i c_ix_i) 的值,记做:
一个线性规划的对偶问题是如下的问题:
有 (m) 个非负变量 (y),以及 (n) 个约束,形如:
要求最小化 (sum_i b_iy_i) 的值,记做:
关于线性规划问题,我们有如下的定理:
弱对偶定理 线性规划问题的解总是小于等于其对偶问题的解。
证明
值得注意的是,弱对偶定理对所有的规划问题都成立,包括但不限于整数规划等。
强对偶定理 线性规划的解等于其对偶问题的解。
这个定理证明较为复杂,这里略去。
不同于弱对偶定理,强对偶定理只对线性规划成立。
我们先来考虑一个线性规划的扩展形式:如何对 (sum_ia_ix_i=b_i) 的条件做对偶?
解决方案是,我们把它拆成如下两个条件:
然后记上面那个条件对偶后的变量为 (y_1),下面的为 (y_2),那么最后要最小化的就是 (b_i(y_1-y_2)),于是我们记 (varphi_i=y_1-y_2),此时 (varphi_i) 就没有 (geq 0) 的限制了。
现在来考虑最大费用循环流的线性对偶形式:
于是直接对偶问题可得:
注意到这个形式下,(x_{u,v}) 可以直接取到最小值,于是就等价于
于是所有这种形式的问题都可以用最大费用循环流建图。
下面是例题。
Problem(「ZJOI2013」防守战线).
长为 (n) 的序列,初始全为 (0),在 (i) 处加 (1) 有 (c_i) 的代价。有 (m) 个限制,形如 ([l_i,r_i]) 的区间和必须 (geq d_i)。求最小代价。
(nleq 1000,mleq 10000)
sol.
记前缀和为 (S_i),于是总的代价为
直接拿着跑最大费用循环流即可。可能需要注意一下建图的方式,根据实际测试,不同的建图方式速度差异足足有 (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.
考虑最大费用循环流的一个特化:有源汇最大费用可行流,其形式如下:
其对偶形式即为:
这道题中,我们考虑最小费用可行流,这就是
这个形式非常像题目的形式。我们令 (s=1,t=n),并定义 (varphi_1) 为 (0),其他点的 (varphi_u) 为 (1) 号点到它的最短路 (dis_{1,u}),于是 (x_{u,v}) 就是我们题目中要求的变化量。然后令 (c_{u,v}=1) 就有:
于是我们就有:
然后注意到 (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));
}
}