P1791 [国家集训队]人员雇佣
这是一篇拥有比较详细的证明的题解。
人员雇佣
给出
n 个人,每个人的雇佣费用为a_i ,两个人之间有价值度E(i,j) 。要求选择一些人,假设选择的集合为
A ,那么总利益就是\sum_{i,j\in A} E(i,j)-\sum_{i\in A,j\not\in A} E(i,j)-\sum_{i\in A} a_i 。求总利益的最大值。
首先通过补集思想转化一下:
其中
题目要求是最大化
这样就是一个比较标准的最小割模型了,容易想到:
- 连边
(S,i,C_i) 。 - 连边
(T,i,a_i) 。 - 对于
E(i,j) ,连边(i,j,2\times E(i,j)) 。
然后用
首先是假设将所有雇员全部雇佣,然后要么减去贡献,要么减去雇佣费用。
相同的,在最小割中,要么直接割掉
要么直接割掉
但是对于第二种的情况,它还是有可能使原图连通(存在连边
所以还需要割掉所有
这恰好对应了,如果选择
另外,这道题没必要使用 long long,但之前为了保险还是开了,导致直接 T 到 50pts,深刻体会到 long long 的效率问题。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int INF = 1 << 30;
const int N = 1010;
const int M = N * N * 2;
int n, a[N], c[N], w[N][N];
int S, T, cnt = 1, head[N], now[N], d[N];
struct Edge{int nxt, to, val;} ed[M];
queue<int> q;
int read() {
int x = 0, f = 1; char c = getchar();
while (c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
while (c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
void add(int u, int v, int w){
ed[++ cnt] = (Edge){head[u], v, w}; head[u] = cnt;
ed[++ cnt] = (Edge){head[v], u, 0}; head[v] = cnt;
}
bool bfs(){
while(!q.empty()) q.pop();
memset(d, 0, sizeof(d));
d[S] = 1, now[S] = head[S]; q.push(S);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = head[u]; i; i = ed[i].nxt){
int v = ed[i].to, w = ed[i].val;
if(w && !d[v]){
d[v] = d[u] + 1;
now[v] = head[v];
if(v == T) return true;
q.push(v);
}
}
}
return false;
}
int Dinic(int u, int flow){
if(u == T) return flow;
int rest = flow; int i;
for(i = now[u]; i && rest; i = ed[i].nxt){
int v = ed[i].to, w = ed[i].val;
if(d[v] == d[u] + 1 && w){
int k = Dinic(v, min(rest, w));
if(!k) d[v] = 0;
ed[i].val -= k;
ed[i ^ 1].val += k;
rest -= k;
}
}
now[u] = i;
return flow - rest;
}
int Max_Flow(){
int Flow = 0, flow;
while(bfs())
while(flow = Dinic(S, INF)) Flow += flow;
return Flow;
}
int main(){
n = read();
for(int i = 1; i <= n; i ++) a[i] = read();
int ans = 0;
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++){
w[i][j] = read();
if(i == j || !w[i][j]) continue;
c[i] += w[i][j]; ans += w[i][j];
add(i, j, 2 * w[i][j]);
}
S = n + 1, T = S + 1;
for(int i = 1; i <= n; i ++)
add(S, i, c[i]), add(i, T, a[i]);
printf("%d\n", ans - Max_Flow());
return 0;
}