[解题报告] CF103E Buying Sets (网络流最小割)

传送🚪

题意

\(n\) 个集合,第 \(i\) 个集合有 \(m_i\) 个小于等于 \(n\) 的正整数元素,保证任意 \(k\) 个集合的并的大小大于等于 \(k\)

每个集合有一个权值,选出若干个集合,使得这若干个集合的并的大小等于集合数量,并且权值和最小,求出这个最小值。

\(m \le n \le 300\)

思路

对于一个集合,如果它被选中了,则它包含的元素都必须被选中,显然可以套到最小割上。

设左部点为集合,右部点为元素,若割掉集合与 \(S\) 的连边则表示不选这个集合;若割掉元素和 \(T\) 的连边就表示选择这个元素。

最小割模型中割掉的边要付出代价,在我们这个模型中就表示不选的集合要付出代价,并使得这个代价最小。而题面要求的是选中的集合的权值和最小,所以我们把权值取反,就转化为了最大收益、最小代价。

现在考虑如何满足 “集合的并的大小等于集合个数” 这个限制。

设被割掉的元素有 \(x\) 个,被割掉的集合有 \(y\) 个。根据题面中 “任意 \(k\) 个集合的并的大小大于等于 \(k\) ” 可以得到不等式 \(n - y \le x\),即 \(x + y \ge n\)。而我们要满足的是 \(n - y = x\), 即 \(x + y = n\),也就是说,我们要使得割掉的边尽量地小。那么,直观地,我们对每条边加上 \(inf\) 的容量,就能保证割掉的边尽量地少。现在割边的总数已经确定为 \(n\) 了,我们只要在最后减去 \(n \times inf\) 就可以得到最小代价。

代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>

using namespace std;

typedef long long ll;

const int _ = 600 + 7;
const int __ = 1e5 + 7;
const int S = 601, T = 602;
const int inf = 0x3f3f3f3f;

int n;
int lst[_], nxt[__], to[__], cap[__], tot = 1;
ll ans;

int gi() {
  int x = 0; bool f = 0; char c = getchar();
  while (!isdigit(c) and c != '-') c = getchar();
  if (c == '-') f = 1, c = getchar();
  while (isdigit(c)) x = (x << 3) + (x << 1) + c - '0', c = getchar();
  return f ? -x : x;
}

void Add(int x, int y, int c) {
  nxt[++tot] = lst[x]; to[tot] = y; cap[tot] = c; lst[x] = tot;
  nxt[++tot] = lst[y]; to[tot] = x; cap[tot] = 0; lst[y] = tot;
}

void Init() {
  n = gi();
  for (int i = 1; i <= n; ++i) {
    int m = gi();
    for (int j = 1; j <= m; ++j) Add(i, gi() + n, inf);
  }
  for (int i = 1; i <= n; ++i) {
    int v = gi(); ans -= v;
    Add(S, i, inf - v), Add(i + n, T, inf);
  }
}

int d[_], cur[_];
queue<int> q;

bool Bfs() {
  memset(d, -1, sizeof d);
  while (!q.empty()) q.pop();
  d[S] = 0, q.push(S);
  while (!q.empty()) {
    int u = q.front(); q.pop();
    for (int i = lst[u]; i; i = nxt[i])
      if (cap[i] and d[to[i]] == -1) {
        d[to[i]] = d[u] + 1, q.push(to[i]);
        if (to[i] == T) return 1;
      }
  }
  return 0;
}

int Dfs(int u, int flow) {
  if (u == T) return flow;
  int res = flow;
  for (int &i = cur[u]; i; i = nxt[i])
    if (cap[i] and d[to[i]] == d[u] + 1) {
      int tmp = Dfs(to[i], min(res, cap[i]));
      cap[i] -= tmp, cap[i ^ 1] += tmp;
      res -= tmp;
      if (!res) return flow;
    }
  return flow - res;
}
  
void Run() {
  ll maxFlow = -1ll * n * inf; int flow;
  while (Bfs()) {
    memcpy(cur, lst, sizeof cur);
    do {
      flow = Dfs(S, inf);
      maxFlow += flow;
    } while (flow);
  }
  cout << -(ans - maxFlow) << endl;
}

int main() {
  Init();
  Run();
  return 0;
}
posted @ 2020-11-26 20:21  BruceW  阅读(98)  评论(0编辑  收藏  举报