The 2019 ICPC Asia Nanjing Regional Contest J.Spy

J. Spy

题目链接:

https://codeforces.com/gym/103466/problem/J

大概题意:

​ 一个团队需要一名茶水员和一名代码员,Amy 和 Bob 分别有 n n n 名茶水员和 n n n 名代码员。现在Bob 还没有组好团队,而 Amy 的团队成员已经组好了。问Bob 在知道 Amy 团队成员的情况下,组队可以获得最大的期望值。

大致想法:

​ 通过模拟可以发现,当 Bob 确定好自己的团队成员的时候,每个团队带来的贡献是 Amy 团队所有比他们小的数量。这里的复杂是 O O O( n l g n nlgn nlgn)

​ 那么确定 Bob 这里的团队构成呢,刚开始想了一个很假的贪心,使 Bob 超过 x x x 的数量尽可能多,然后去枚举 x x x 。这里的总复杂度是 O O O( n 3 n^3 n3 l g n lgn lgn) 看起来复杂度是对的 但是这个贪心是不对的。

​ 正确的做法是建图连边,以 B i B_i Bi 代表 Bob 的第 i i i 个茶水员,以 C j C_j Cj 代表Bob的第 j j j 个代码员,先算出 V i j V_{ij} Vij 的权值,这个权值就是团队 Amy 所有小于他们的数量。建完图跑一次最大流,跑出来的答案就是我们最后要求解的答案。

代码

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

template <typename T>
struct hungarian {  // km
  int n;
  vector<int> matchx;  // 左集合对应的匹配点
  vector<int> matchy;  // 右集合对应的匹配点
  vector<int> pre;     // 连接右集合的左点
  vector<bool> visx;   // 拜访数组 左
  vector<bool> visy;   // 拜访数组 右
  vector<T> lx;
  vector<T> ly;
  vector<vector<T> > g;
  vector<T> slack;
  T inf;
  T res;
  queue<int> q;
  int org_n;
  int org_m;

  hungarian(int _n, int _m) {
    org_n = _n;
    org_m = _m;
    n = max(_n, _m);
    inf = numeric_limits<T>::max();
    res = 0;
    g = vector<vector<T> >(n, vector<T>(n));
    matchx = vector<int>(n, -1);
    matchy = vector<int>(n, -1);
    pre = vector<int>(n);
    visx = vector<bool>(n);
    visy = vector<bool>(n);
    lx = vector<T>(n, -inf);
    ly = vector<T>(n);
    slack = vector<T>(n);
  }

  void addEdge(int u, int v, int w) {
    g[u][v] = max(w, 0);  // 负值还不如不匹配 因此设为0不影响
  }

  bool check(int v) {
    visy[v] = true;
    if (matchy[v] != -1) {
      q.push(matchy[v]);
      visx[matchy[v]] = true;  // in S
      return false;
    }
    // 找到新的未匹配点 更新匹配点 pre 数组记录着"非匹配边"上与之相连的点
    while (v != -1) {
      matchy[v] = pre[v];
      swap(v, matchx[pre[v]]);
    }
    return true;
  }

  void bfs(int i) {
    while (!q.empty()) {
      q.pop();
    }
    q.push(i);
    visx[i] = true;
    while (true) {
      while (!q.empty()) {
        int u = q.front();
        q.pop();
        for (int v = 0; v < n; v++) {
          if (!visy[v]) {
            T delta = lx[u] + ly[v] - g[u][v];
            if (slack[v] >= delta) {
              pre[v] = u;
              if (delta) {
                slack[v] = delta;
              } else if (check(v)) {  // delta=0 代表有机会加入相等子图 找增广路
                                      // 找到就return 重建交错树
                return;
              }
            }
          }
        }
      }
      // 没有增广路 修改顶标
      T a = inf;
      for (int j = 0; j < n; j++) {
        if (!visy[j]) {
          a = min(a, slack[j]);
        }
      }
      for (int j = 0; j < n; j++) {
        if (visx[j]) {  // S
          lx[j] -= a;
        }
        if (visy[j]) {  // T
          ly[j] += a;
        } else {  // T'
          slack[j] -= a;
        }
      }
      for (int j = 0; j < n; j++) {
        if (!visy[j] && slack[j] == 0 && check(j)) {
          return;
        }
      }
    }
  }

  void solve() {
    // 初始顶标
    for (int i = 0; i < n; i++) {
      for (int j = 0; j < n; j++) {
        lx[i] = max(lx[i], g[i][j]);
      }
    }

    for (int i = 0; i < n; i++) {
      fill(slack.begin(), slack.end(), inf);
      fill(visx.begin(), visx.end(), false);
      fill(visy.begin(), visy.end(), false);
      bfs(i);
    }

    // custom
    for (int i = 0; i < n; i++) {
      if (g[i][matchx[i]] > 0) {
        res += g[i][matchx[i]];
      } else {
        matchx[i] = -1;
      }
    }
    cout << res << "\n";
    /*
    for (int i = 0; i < org_n; i++) {
      cout << matchx[i] + 1 << " ";
    }
    cout << "\n";
    */
  }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    int n;
    cin >> n;
    vector<ll>  b(n), c(n);
    vector<pair<ll, ll>> a(n);
    for (int i = 0; i < n; i++) cin >> a[i].first;
    for (int i = 0; i < n; i++) cin >> a[i].second;
    for (int i = 0; i < n; i++) cin >> b[i];
    for (int i = 0; i < n; i++) cin >> c[i];
    sort(a.begin(), a.end());
    vector<ll> qz;
    vector<int> qzv;
    qz.push_back(a[0].first);
    qzv.push_back(a[0].second);
    for (int i = 1; i < n; i++) {
        qz.push_back(a[i].first);
        qzv.push_back(a[i].second + qzv.back());
    }
    hungarian<int> km(n, n);
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            int w = 0;
            ll x = b[i] + c[j];
            auto it = lower_bound(qz.begin(), qz.end(), x);
            if (it != qz.begin()) {
                it--;
                w = qzv[it - qz.begin()];
            }
            km.addEdge(i, j, w);
        }
    }
    km.solve();
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值