1. 103E

大意: 给定$n$个集合, 满足对于任意的$k$, 任意$k$个集合的并集都不少于$k$. 要求选出$k$个集合$(k> 0)$, 使得并恰好等于$k$, 输出最少花费.

Hall定理: 二分图的$X$部到$Y$部有完美匹配等价于$X$中任意$k$个点与$Y$中至少$k$个点相邻.

所有集合为$X$部, 每个数为$Y$部, 集合向所含数连边, 那么一定存在完美匹配. 假设求出一组匹配, 数字$i$对应集合$C_i$, 那么最终若选取集合$A_i$, 则$A_i$中所有数字$x$对应的集合$C_x$一定也要选, 把权值取负就转化为最大权闭合子图模型. 对于最大权闭合子图问题, 源点连所有正权点, 容量为该点权值, 所有负权点连汇点, 容量为该点权值的绝对值, 其余边与原图一样, 容量为无穷, 求出源点到汇点的最小割, 那么答案为正权和-最小割, 与源点间的割的含义为不选择该点, 与汇点间的割的含义为选择该点.

#include <iostream>
#include <cstdio>
#include <queue>
#define REP(i,a,n) for(int i=a;i<=n;++i)
#define pb push_back
using namespace std;

const int N = 1e6+10, INF = 0x3f3f3f3f;
int n, S, T, clk, f[N], vis[N];
vector<int> g[N];
int dfs(int x) {
    for (int y:g[x]) if (vis[y]!=clk) {
        vis[y] = clk;
        if (!f[y]||dfs(f[y])) return f[y]=x;
    }
    return 0;
}
struct edge {
    int v,w,next;
} e[N];
int head[N], dep[N], cur[N], cnt=1;
queue<int> Q;
void add(int u, int v, int w) {
    e[++cnt] = {v,w,head[u]};
    head[u] = cnt;
    e[++cnt] = {u,0,head[v]};
    head[v] = cnt;
}
int bfs() {
    REP(i,1,T) dep[i]=INF,vis[i]=0,cur[i]=head[i];
    dep[S]=0,Q.push(S);
    while (Q.size()) {
        int u = Q.front(); Q.pop();
        for (int i=head[u]; i; i=e[i].next) {
            if (dep[e[i].v]>dep[u]+1&&e[i].w) {
                dep[e[i].v]=dep[u]+1;
                Q.push(e[i].v);
            }
        }
    }
    return dep[T]!=INF;
}
int dfs(int x, int w) {
    if (x==T) return w;
    int used = 0;
    for (int i=cur[x]; i; i=e[i].next) {
        cur[x] = i;
        if (dep[e[i].v]==dep[x]+1&&e[i].w) {
            int flow = dfs(e[i].v,min(w-used,e[i].w));
            if (flow) {
                used += flow;
                e[i].w -= flow;
                e[i^1].w += flow;
                if (used==w) break;
            }
        }
    }
    return used;
}
int dinic() {
    int ans = 0;
    while (bfs()) ans+=dfs(S,INF);
    return ans;
}


int main() {
    scanf("%d", &n), S = n+1, T = S+1;
    REP(i,1,n) {
        int k, t;
        scanf("%d", &k);
        while (k--) {
            scanf("%d", &t);
            g[i].pb(t+n);
            g[t+n].pb(i);
        }
    }
    REP(i,1,n) ++clk, dfs(i);
    int sum = 0;
    REP(i,1,n) {
        int t;
        scanf("%d", &t);
        t = -t;
        if (t>=0) add(S,i,t),sum+=t;
        else add(i,T,-t);
    }
    REP(i,1,n) for (int j:g[i]) add(i,f[j],INF);
    printf("%d\n", dinic()-sum);
}
View Code

相关文章:

  • 2021-07-02
  • 2021-11-15
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2021-11-30
  • 2021-08-29
  • 2021-11-01
  • 2021-07-11
  • 2022-01-07
  • 2021-08-22
  • 2021-11-07
相关资源
相似解决方案