题面
分析
这类题叫最大权闭合图问题。
有向图的闭合图是指每个点的后驱节点都在图中。
这道题中,一些实验和它对应的仪器形成了个闭合二分图,一边点权是正,一边是负,总收益是权值和。
怎么最大化权值和呢?
建超级源点 s 和超级汇点 t
从 s 到每个实验连边,边权为 ai
从 每个仪器到 t 连边,边权为 bi 的绝对值
每个实验向对应仪器连边,边权为 inf
那么 最大权值和 = 正权和 - 最小割
为什么捏?
选择了哪些实验方案和它对应的仪器形成了一个闭合图,这个闭合图的权值是与 s 相连的正权和减去不与 t 相连的负权绝对值的和
而一个割的权值是 不与 s 相连的正权和加上不与 t 相连的负权绝对值的和
于是 闭合图权值 + 对应的割的权值 = 正权和
那么 最大权闭合图就是最小割割完后的与 s 相连的点集
代码如下
#include <bits/stdc++.h>
#define N 500005
#define inf 2147483647
#define LL long long
using namespace std;
struct node{
int a, b, c, n;
}d[N * 2];
int dep[N], h[N], cur[N], a[105][105], n, m, f[N], vis[55], cnt = 1, tot, s, t, sum;
LL ans, ans1;
void cr(int a, int b, int c){
d[++cnt].a = a; d[cnt].b = b; d[cnt].c = c; d[cnt].n = h[a]; h[a] = cnt;
}
void lk(int a, int b, int c){
cr(a, b, c);
cr(b, a, 0);
}
int bfs(){
int i, a, b, c;
memset(dep, 0, sizeof(dep));
for(i = 0; i <= tot; i++) cur[i] = h[i];
queue<int> q;
q.push(s);
dep[s] = 1;
while(!q.empty()){
a = q.front();
q.pop();
for(i = h[a]; i; i = d[i].n){
b = d[i].b;
c = d[i].c;
if(!dep[b] && c){
dep[b] = dep[a] + 1;
q.push(b);
}
}
}
return dep[t];
}
int dfs(int a, int flow){
int i, b, c, w, used = 0;
if(a == t) return flow;
for(i = cur[a]; i; i = d[i].n){
cur[a] = i;
b = d[i].b;
c = d[i].c;
if(dep[b] == dep[a] + 1 && c > 0){
if(w = dfs(b, min(flow - used, c))){
used += w;
d[i].c -= w;
d[i ^ 1].c += w;
}
if(used == flow) break;
}
}
return used;
}
/*void dfss(int a){ int i, b, c; if(a <= n) printf("%d ", a); else vis[a - n] = 1; for(i = h[a]; i; i = d[i].n){ b = d[i].b; c = d[i].c; if(c > 0) dfss(b); } }*/
int main(){
int i, j, b, c;
scanf("%d%d", &n, &m);
tot = n + m + 2;
s = n + m + 1, t = n + m + 2;
for(i = 1; i <= n; i++){
scanf("%d", &j);
sum += j;
lk(s, i, j);
char tools[10000];
memset(tools,0,sizeof tools);
cin.getline(tools,10000);
int ulen=0,tool;
while (sscanf(tools+ulen,"%d",&tool)==1)
{
lk(i, tool + n, inf);
a[i][tool] = 1;
if (tool==0)
ulen++;
else {
while (tool) {
tool/=10;
ulen++;
}
}
ulen++;
}
}
for(i = 1; i <= m; i++){
scanf("%d", &j);
lk(i + n, t, j);
}
while(bfs()) ans += dfs(s, inf);
for(i = 1; i <= n; i++) if(dep[i]) printf("%d ", i);
printf("\n");
for(i = 1; i <= m; i++) if(dep[i + n]) printf("%d ", i);
printf("\n");
printf("%d", sum - ans);
return 0;
}