HOJ 2634 How to earn more [网络流]最大权闭合图

题目: LINK

题意:有m个项目要做,有n个工人,每个项目要且必须要这些工人中的一个子集来完成, 一个工人可以参与多个项目,给出每个项目完成后的收益gi和雇佣每个工人的花费ci,以及每个项目必须需要的工人的编号,问能获得的最大收益是多少。
闭合图的概念:在一个图中,我们选取一些点构成集合,记为V,且集合中的出边(即集合中的点的向外连出的弧),所指向的终点(弧头)也在V中,则我们称V为闭合图。
最大权闭合图即在所有闭合图中,集合中点的权值之和最大的V,我们称V为最大权闭合图。

建图:设立源点S,汇点T。s到每个项目连边容量为项目的收益,每个工人到T连边容量为工人的花费,对于每个项目需要的工人之间连边,容量为无穷大.
可以求得最小割(最大流)[S,T];  最终要求的结果为sum - [S,T]. (sum为所有项目收益的和)
为什么要这么做呢?
对于建立的图的每一个割,都会对应一个闭合图,即[S,T]分割成的包含S的集合,这个闭合图就是包含S的部分。
[S,T]中的边要么是与S相连,要么是和T相连,因为其他边的容量都是INF。
结果ans = A' - B'  (A'不在最小割中的项目的和, B'是在最小割中的工人的花费和);
[S, T] = A - A' + B' (A是所有项目的收益和); 
所以ans = A - [S,T];

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
#include <cmath>
#include <queue>
#include <map>
#include <set>
using namespace std; 
#define INF 1000000000
//typedef __int64 LL; 
#define N 110
#define M 222
int t, n, m, a[N], b[N], tot, hh[M], dis[M], lev[M], S, T; 
struct node {
    int u, v, w, next;  
}edge[100001]; 
void init() {
    memset(hh, -1, sizeof(hh)); 
    tot = 0; 
}
void add(int u, int v, int w){
    edge[tot].u = u; edge[tot].v = v; 
    edge[tot].w = w; edge[tot].next = hh[u]; 
    hh[u] = tot ++; 
}
int bfs() {
    queue<int > Q; 
    memset(lev, -1, sizeof(lev)); 
    Q.push(S); lev[S] = 0; 
    while(!Q.empty()) {
        int u = Q.front(); Q.pop(); 
        for(int i = hh[u]; i != -1; i = edge[i].next) {
            int v = edge[i].v; 
            if(edge[i].w && lev[v] == -1) {
                lev[v] = lev[u] + 1; 
                Q.push(v); 
            }
        }
    }
    return lev[T] != -1; 
}
int dfs(int u, int flow) {
    if(u == T) return flow; 
    int tmp = flow, ad; 
    for(int i = hh[u]; i != -1; i = edge[i].next) {
        int v = edge[i].v; 
        if(lev[v] == lev[u] + 1 && tmp > 0 && edge[i].w) {
            ad = dfs(v, min(tmp, edge[i].w)); 
            if(!tmp) break; 
            edge[i].w -= ad; 
            edge[i^1].w += ad; 
            tmp -= ad; 
        }
    }
    if(ad == 0) lev[u] = -1; 
    return flow- tmp; 
}
int dinic() {
    int ret = 0, tmp; 
    while(bfs())  while(tmp = dfs(S, INF)) ret += tmp;  
    return ret; 
}

int main() 
{
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin); 
#endif // ONLINE_JUDGE
    scanf("%d", &t); 
    while(t -- ) {
        scanf("%d%d", &m, &n); 
        S = n + m + 1; 
        T = n + m + 2; 
        int tmp , sum = 0, gg;
        init(); 
        for(int i = 0; i < m; i++) {
            scanf("%d", &a[i]); 
            sum += a[i] ; 
            add(S, i, a[i]); add(i, S, 0); 
        }
        for(int i = 0; i < n; i++) {
            scanf("%d", &b[i]); 
            add(i+m, T, b[i]); add(T, i+m, 0); 
        }
        for(int i = 0; i < m; i++) {
            scanf("%d", &tmp); 
            for(int j = 1; j <= tmp; j++) { scanf("%d", &gg); 
                add(i, gg+m, INF); add(gg+m, i, 0); 
            }
        }
        int ans = dinic(); 
        printf("%d\n", sum - ans); 
    }    
    return 0; 
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值