【最小割】【网络流24题】【P2762】 太空飞行计划问题
Description
W 教授正在为国家航天中心计划一系列的太空飞行。每次太空飞行可进行一系列商业性实验而获取利润。现已确定了一个可供选择的实验集合E={E1,E2,…,Em},和进行这些实验需要使用的全部仪器的集合I={I1,I2,…In}。实验Ej需要用到的仪器是I的子集Rj。配置仪器Ik的费用为ck美元。实验Ej的赞助商已同意为该实验结果支付pj美元。W教授的任务是找出一个有效算法,确定在一次太空飞行中要进行哪些实验并因此而配置哪些仪器才能使太空飞行的净收益最大。这里净收益是指进行实验所获得的全部收入与配置仪器的全部费用的差额。
对于给定的实验和仪器配置情况,编程找出净收益最大的试验计划。
Limitation
\(1~\leq~n,~m~\leq~50\)
Solution
为啥题解里没人讲证明啊QAQ
简化一下题意以后,发现题意是这样的:
给定一张图,有左侧的点和右侧的点,左侧的点点权为正(对应试验),右侧的点点权为负(对应器材),如果选择了左侧的某个点就必须要选右边的一部分点。要求最大化点权和。
如果将左侧的点和右侧的点之间对应连边,如果该实验要求该器材就连一条边,那么问题就被转化为了这样一个问题:
给定一个有向图,点有点权,选择一个子图,满足子图上如果选择了一个点就必须选择它后继的所有点。最大化点权和。
这是一个经典的网络流问题,如果一个点被选择了则后继必须被选择,那么称该图是 闭合的,因此该问题叫做最大权闭合子图问题。可以使用最小割解决。
具体的建图方法为:
源点向所有正权点连结一条容量为权值的边
保留原图中所有的边,容量为正无穷
所有负权点向汇点连结一条容量为权值绝对值的边
则原图的最大权闭合子图的点权和即为所有正权点权值之和减去建出的网络流图的最小割。
以下约定源点为 \(s\),汇点为 \(t\)。
在最小割图上,如果割掉 \(s\) 和 \(u\) 之间的边,代表不选择 \(u\) 进入子图,如果割掉 \(v\) 和 \(t\) 之间的边,代表选择 \(v\) 进入子图。
求完最小割后,如果点 \(i\) 与 \(s\) 相连,那么子图上会选择点 \(i\),如果 \(i\) 与 \(t\) 相连,则不选择点 \(i\)。
考虑证明:
(部分证明内容参考CaptainChen的博客)
先证明得到的子图是闭合的:
首先考虑由于求得是最小割,一个点要么和 \(s\) 相连,要么和 \(t\) 相连,否则一定割掉它向 \(s\) 或 \(t\) 的一条边是没有意义的,因为割掉该边不会改变图的不连通性,最小割不会割掉它。
由于原图中的边全部是正无穷,最小割只会割掉源点和正权点之间或负权点和汇点之间的边。
考虑如果选择了正权点 \(u\),为了保证 \(s-t\) 不连通,必须割掉 \(u\) 所有后继中的负权点。这证明了如果选择了一个正权点那么所有的后继负权点都会被选择。
如果选择了正权点 \(u\),设 \(v\) 是 \(u\) 的后继且 \(v\) 的的权值为正,由于没有割掉 \(u\),通过 \(u-v\) 之间的正无穷边总能使得 \(s-v\) 联通,于是割掉 \(s-v\) 的边是没有意义的,最小割不会割掉这条边,这证明了如果选择了一个正权点那么该点的所有后继正权点都会被选择。
点权为 \(0\) 的情况同理。
考虑事实上选择的闭合子图的过程是不可能从一个负权点开始的,因为去掉这个负权点直接选择它的后继显然优于选择该点。于是只考虑选择正权点就可以包括所有的情况。证毕。
再证明得到的是最大权子图:
考虑如果 \(i\) 与 \(s\) 联通,那么选择 \(i\),否则不选择 \(i\)。所以最小割割掉的权值和是 不被选择的正权点权值和 + 被选择的负权点的权值的绝对值和 ,即 最小割 = \(\min\{\)没被选择的正权点权值和 + 被选择的负权点的权值的绝对值和\(\}\)
于是
证毕。
于是本题只需要按照上述方法建图即可。输出方案只需要输出与 \(s\) 联通的点。
Code
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <algorithm>
#ifdef ONLINE_JUDGE
#define freopen(a, b, c)
#endif
typedef long long int ll;
namespace IPT {
const int L = 1000000;
char buf[L], *front=buf, *end=buf;
char GetChar() {
if (front == end) {
end = buf + fread(front = buf, 1, L, stdin);
if (front == end) return -1;
}
return *(front++);
}
}
template <typename T>
inline bool qr(T &x) {
char ch = IPT::GetChar(), lst = ' ';
while ((ch > '9') || (ch < '0')) lst = ch, ch=IPT::GetChar();
while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = IPT::GetChar();
if (lst == '-') x = -x;
return (ch != '\r') && (ch != '\n');
}
namespace OPT {
char buf[120];
}
template <typename T>
inline void qw(T x, const char aft, const bool pt) {
if (x < 0) {x = -x, putchar('-');}
int top=0;
do {OPT::buf[++top] = static_cast<char>(x % 10 + '0');} while (x /= 10);
while (top) putchar(OPT::buf[top--]);
if (pt) putchar(aft);
}
const int maxn = 110;
const int INF = 10000000;
struct Edge {
int u, v, flow;
Edge *nxt, *bk;
Edge(const int _u, const int _v, const int _fl, Edge* &h) {
this->u = _u; this->v = _v; this->flow = _fl; this->nxt = h; h = this;
}
};
Edge *hd[maxn], *fir[maxn];
inline void cont(const int _u, const int _v, const int _flow) {
auto u = new Edge(_u, _v, _flow, hd[_u]), v = new Edge(_v, _u, 0, hd[_v]);
(u->bk = v)->bk = u;
}
int n, m, s, t, ans;
int MU[maxn], val[maxn], trial[maxn], tol[maxn], dist[maxn];
std::vector<int>tool[maxn];
std::queue<int>Q;
bool bfs();
int dfs(const int u, int canag);
int main() {
freopen("1.in", "r", stdin);
qr(m); qr(n);
for (int i = 1, x = 0; i <= m; ++i) {
qr(MU[i]); ans += MU[i];
bool k;
do {
k = qr(x);
tool[i].push_back(x);
x = 0;
} while (k);
trial[i] = ++t;
}
for (int i = 1; i <= n; ++i) {qr(val[i]); tol[i] = ++t;}
s = ++t; ++t;
for (int i = 1; i <= m; ++i) {
cont(s, trial[i], MU[i]);
for (auto j : tool[i]) cont(trial[i], tol[j], INF);
}
for (int i = 1; i <= n; ++i) cont(tol[i], t, val[i]);
while (bfs()) {
for (int i = 1; i <= t; ++i) fir[i] = hd[i];
ans -= dfs(s, INF);
}
for (int i = 1; i <= m; ++i) if (dist[i]) qw(i, ' ', true);
putchar('\n');
for (int i = tol[1]; i <= tol[n]; ++i) if (dist[i]) qw(i - m, ' ', true);
putchar('\n');
qw(ans, '\n', true);
return 0;
}
bool bfs() {
memset(dist, 0, sizeof dist);
Q.push(s); dist[s] = 1;
while (!Q.empty()) {
int u = Q.front(); Q.pop();
for (auto e = hd[u]; e; e = e->nxt) if (e->flow > 0) {
if (dist[e->v]) continue;
dist[e->v] = dist[u] + 1;
Q.push(e->v);
}
}
return dist[t];
}
int dfs(const int u, int canag) {
if ((u == t) || (!canag)) return canag;
int _flow = 0;
for (auto &e = fir[u]; e; e = e->nxt) if (e->flow > 0) {
int v = e->v;
if (dist[v] != (dist[u] + 1)) continue;
int f = dfs(v, std::min(canag, e->flow));
_flow += f; e->flow -= f; e->bk->flow += f;
if (!(canag -= f)) break;
}
return _flow;
}