题意:
公司要进行裁员,裁掉一个人可以获得一定的利益或损失(用正数和负数表示)。
一些人之间有附属关系,比如b是a的下属,则裁掉a就必须裁掉b。
问要取得最大的利益,最少要裁掉几个人,取得的最大利益是多少。
思路:
先说明什么是最大权闭合图:
在一个图中,我们选取一些点构成集合,记为V,且集合中的出边(即集合中的点的向外连出的弧),所指向的终点(弧头)也在V中,则我们称V为闭合图。最大权闭合图即在所有闭合图中,集合中点的权值之和最大的V,我们称V为最大权闭合图。如下图:
集合{5}、{2,5}、{4,5}、{2,4,5}、{3,4,5}、{1,2,3,4,5}、{1,2,4,5} 都是闭合图,其中{3,4,5}是最大权闭合图。
如何求最大权闭合图:
1.在原图中添加s和t
2. 原图中的边权全部设为INF
3. s与原图中权值为正的点连边,权值为点的权值
4. 不满足3的点与t连边,权值为点的权值的绝对值
最大权闭合图的的权=原图中权值为正的点的和 - 最小割(最大流)
详细的内容和证明可以参考胡伯涛的论文《最小割模型在信息学竞赛中的应用》。
再回到本题,第二问求最大利益,显然就是最大权闭合图。
第一问问最少裁掉几个人,我采用的是边权放大的方法:
所有上述3中的边扩大一个很大的倍数k,再减去1,所有上述4中的边扩大同样k倍,再加上1。
设num为一共减去了多少个1,则(maxflow + num) / k仍是最大权闭合图的权,而(maxFlow + num) % k就是第一问的答案。
代码(3332K,3422MS):
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
#include <queue>
#define T (n + 1)
const long long INF = 1e16;
using namespace std;
struct Edge{
int from, to;
long long cap;
Edge() {}
Edge(int a, int b, long long c) : from(a), to(b), cap(c) {}
};
int n, m;
vector<Edge> edges;
vector<int> G[5005];
void addEdge(int from, int to, long long cap) {
edges.push_back(Edge(from, to, cap));
edges.push_back(Edge(to, from, 0));
int siz = edges.size();
G[from].push_back(siz - 2);
G[to].push_back(siz - 1);
}
int cur[5005];
int layer[5005];
bool build() {
memset(layer, -1, sizeof(layer));
queue<int> q;
layer[0] = 0;
q.push(0);
while (!q.empty()) {
int current = q.front();
q.pop();
for (int i = 0; i < G[current].size(); i++) {
Edge e = edges[G[current][i]];
if (layer[e.to] == -1 && e.cap > 0) {
layer[e.to] = layer[current] + 1;
q.push(e.to);
}
}
}
return layer[T] != -1;
}
long long find(int x, long long curFlow) {
if (x == T || !curFlow) return curFlow;
long long flow = 0, f;
for (int &i = cur[x]; i < G[x].size(); i++) {
Edge &e = edges[G[x][i]];
if (layer[e.to] == layer[x] + 1
&& (f = find(e.to, min(curFlow, e.cap)))) {
e.cap -= f;
edges[G[x][i] ^ 1].cap += f;
flow += f;
curFlow -= f;
if (!curFlow) break;
}
}
return flow;
}
long long dinic() {
long long ans = 0;
while (build()) {
memset(cur, 0, sizeof(cur));
ans += find(0, INF);
}
return ans;
}
int main() {
while (~scanf("%d %d", &n, &m)) {
for (int i = 0; i <= n; i++)
G[i].clear();
edges.clear();
int a, b;
long long x;
long long sum = 0, num = 0;
long long k = 6000;
for (int i = 1; i <= n; i++) {
cin >> x;
if (x > 0) {
sum += x;
num++;
addEdge(0, i, (long long)x * k - 1);
} else addEdge(i, T, (long long)-x * k + 1);
}
for (int i = 0; i < m; i++) {
scanf("%d %d", &a, &b);
addEdge(a, b, INF);
}
long long maxFlow = dinic();
cout << (maxFlow + num) % k << ' ' << sum - (maxFlow + num) / k << endl;
}
return 0;
}