题解 AGC034D【Manhattan Max Matching】

题解 AGC034D【Manhattan Max Matching】

problem

在一个二维坐标系内,点 \((RX_i,RY_i)\) 上有 \(RC_i\) 个红球,点 \((BX_i,BY_i)\) 上有 \(BC_i\) 个蓝球,且保证 \(\sum_{i=1}^{n}RC_i=\sum_{i=1}^{n}BC_i\)

现在要你将这些红球蓝球一一配对,配对的价值为两球所在点之间的曼哈顿距离,请你求出配对完它们的最大价值和。

  • $ \xcancel{1\ \leq\ N\ \leq\ 1000 }\to \sum_{i=1}^{N}\ RC_i \leq 10^5$
  • $ 0\ \leq\ RX_i,RY_i,BX_i,BY_i\ \leq\ 10^9 $
  • $ 1\ \leq\ RC_i,BC_i\ \leq\ 10 $
  • $ \sum_{i=1}^{N} RC_i=\sum_{i=1}^{N}BC_i $
  • 入力される値はすべて整数である。

solution 0

考虑费用流,因为我们观察到如下事实:

\[\begin{aligned} \lvert a - b \rvert &= \max(a - b, b - a)\\ \max(a, b) + \max(c, d) &= \max(a + c, a + d, b + c, b + d)\\ \implies\max(|x_1 - x_2|, |y_1 - y_2|) &= \max(x_1+y_1-x_2-y_2, x_1-y_1-x_2+y_2, -x_1+y_1+x_2-y_2, -x_1-y_1+x_2+y_2) \end{aligned} \]

也就是说这个曼哈顿距离可以转化为四种情况取 \(\max\)。由于这题恰好也是最大,所以直接最大费用最大流即可,中间四个点,两边连边,连边费用是 \(\pm x\pm y\) 之类的东西,然后要有四个点(下文称作【颜色】)的区分。

solution 1

因为原图流量上限都为 \(1\),考虑模拟费用流,首先先声明两个事实:

  • 这个图是没有正环的(费用流不知名定理:原图没有正环则增广后也没有),因此增广路不应该经过重复点
  • 一个点被匹配某个颜色后,就不会再次变成未匹配状态,因为如果变成未匹配相当于反悔反到源点 / 汇点,经过了重复点。所以反悔应该是换颜色。

考虑一条增广路的走法,它总是形如:

  • 先从源点经过一个球走到一个颜色 \(u\)
  • \(u\) 出发兜兜转转,做一堆反悔,到达颜色 \(v\)
  • \(v\) 出发经过一个球走到汇点。

举个例子,这个增广路可能是这样扭曲的:

可以看到这里 \(u=1, v = 0\)。中间增广路可以经过左部点,可以经过右部点,但关键是增广路长度是 \(O(\text{颜色数})\) 的(颜色数),也就是说我们也许可以大力找出增广路去增广,而不是每一次都 spfa。


typedef priority_queue<pair<LL, int>> pqueue

我们开很多个 pqueue qm[2][4], qf[2][4][4],分别表示:

  • qm[0][u] 表示左部点未匹配的点,到达颜色 \(u\) 的费用的一个集合,就是形如 \(\{(w_L(x,u), x):x\in unmatched\}\) 的集合,其中 \(w_L(x, u)\) 是左部点 \(x\) 到颜色 \(u\) 的费用,的边权。qm[1][v] 就是右部点了。
  • qf[0][u][v] 表示只考虑左部点决策,可以将匹配颜色 \(u\) 反悔成 \(v\) 的集合,形式化的:
    • \(\{(w_L(x, v)-w_L(x, u), x): match_x=u\}\)
  • 这里有必要给一下 qf[1][u][v] 的形式化定义,它的增广方向是反过来的:
    • \(\{(w_R(y, u)-w_R(y, v), y): match_y=v\}\)

那么考虑增广路是怎么走的:

  • 先从源点经过一个球走到一个颜色 \(u\)。拿出 qm[0][u].top(),注意你可能需要懒惰删除一下,把已经不在集合中的决策删掉。
  • \(u\) 出发兜兜转转,做一堆反悔,到达颜色 \(v\)
    • 可以枚举所有可能的路径,取出 max(qf[0][u][v], qf[1][u][v]) 作为一次反悔的边权。注意懒惰删除。
    • 也可以取 max(qf[0][u][v], qf[1][u][v]) 作为边权,跑 floyd 最长路,同时记录路径,利于以后反悔。
  • \(v\) 出发经过一个球走到汇点。拿出 qm[1][v].top(),同样懒惰删除。

然后跑这条增广路,只需要更新一下沿途走过的颜色产生的新的决策,加入 pqueue 中。

复杂度大约是 \(O(n\log n)\) 的,但是这个常数有点逆天,可能最多是 \(4^3\) 吧?跑得很快很快。

code

费用流

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <functional>
#include <queue>
#include <utility>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
#define x first
#define y second
typedef long long LL;
// template <int N>
// struct flower {
//  int b[N + 10], cnt;
//  flower() : cnt(0) {}
//  void operator+=(int x) { b[++cnt] = x; }
//  void build() {
//    sort(b + 1, b + cnt + 1), cnt = unique(b + 1, b + cnt + 1) - b - 1;
//  }
//  int operator()(int x) { return lower_bound(b + 1, b + cnt + 1, x) - b; }
//  int operator[](int i) { return b[i]; }
//};
template <int N, int M, class T = int>
struct graph {
  int head[N + 10], nxt[M * 2 + 10], cnt, tot;
  struct edge {
    int u, v;
    T w;
    edge(int u = 0, int v = 0, T w = {0, 0}) : u(u), v(v), w(w) {}
  } e[M * 2 + 10];
  graph() { memset(head, tot = cnt = 0, sizeof head); }
  edge& operator[](int i) { return e[i]; }
  void add(int u, int v, T w) {
    e[++cnt] = edge(u, v, w), nxt[cnt] = head[u], head[u] = cnt;
  }
  void link(int u, int v, T w) { add(u, v, w), add(v, u, w); }
};
template <int N, int M, class T = int, class F = int>
struct mcmf_g : public graph<N, M, pair<T, F>> {
  graph<N, M, pair<T, F>>& g = *this;
  mcmf_g() { g.add(0, 0, {0, 0}); }
  void insert(int u, int v, T w, F c) {
    debug("insert(%d,%d,%d,%d)\n", u, v, w, c);
    g.add(u, v, {w, c});
    g.add(v, u, {0, -c});
  }
  F dis[N + 10];
  int cur[N + 10], vis[N + 10];
  bool bfs(int s, int t) {
    queue<int> q;
    memset(dis, 0x3f, sizeof dis), memset(vis, 0, sizeof vis);
    for (q.push(s), dis[s] = 0; !q.empty();) {
      int u = q.front();
      q.pop(), vis[u] = 0;
      for (int i = g.head[u]; i; i = g.nxt[i]) {
        int v = g[i].v;
        if (!g[i].w.first) continue;
        if (dis[v] > dis[u] + g[i].w.second)
          dis[v] = dis[u] + g[i].w.second, !vis[v] && (q.push(v), vis[v] = 1);
      }
    }
    return dis[t] != dis[0];
  }
  T dfs(int u, T flow, int t) {
    if (u == t || !flow) return flow;
    T rest = flow;
    vis[u] = 1;
    for (int& i = cur[u]; i; i = g.nxt[i]) {
      int v = g[i].v;
      if (dis[v] != dis[u] + g[i].w.second || vis[v] || !g[i].w.first) continue;
      T run = dfs(v, min(rest, g[i].w.first), t);
      if (g[i].w.first -= run, g[i ^ 1].w.first += run, !(rest -= run)) break;
    }
    if (rest == flow) vis[u] = 0;
    return flow - rest;
  }
  pair<T, F> mcmf(int s, int t, T inf) {
    pair<T, F> flow = {0, 0};
    while (bfs(s, t)) {
      memcpy(cur, g.head, sizeof cur);
      T run = dfs(s, inf, t);
      flow.first += run, flow.second += run * dis[t];
    }
    return flow;
  }
};
mcmf_g<10010, 20010, LL, LL> g;
int n, rcnt[1010], bcnt[1010];
int rid[1010], bid[1010], id[2010], S = ++g.tot, T = ++g.tot;
pair<int, int> rd[1010], bd[1010];
// flower<2010> b;
// void work(function<int(pair<int, int>)> calc) {
//  b.cnt = 0;
//  for (int i = 1; i <= n; i++) b += calc(rd[i]), b += calc(bd[i]);
//  b.build();
//  for (int i = 1; i <= b.cnt; i++) id[i] = ++g.tot;
//  for (int i = 1; i < b.cnt; i++)
//    g.insert(id[i], id[i + 1], 1e9, -(b[i + 1] - b[i]));
//  for (int i = 1; i <= n; i++) g.insert(rid[i], id[b(calc(rd[i]))], 1e9, 0);
//  for (int i = 1; i <= n; i++) g.insert(id[b(calc(bd[i]))], bid[i], 1e9, 0);
//  for (int i = 1; i <= b.cnt; i++) id[i] = ++g.tot;
//  for (int i = 1; i < b.cnt; i++)
//    g.insert(id[i + 1], id[i], 1e9, -(b[i + 1] - b[i]));
//  for (int i = 1; i <= n; i++) g.insert(rid[i], id[b(calc(rd[i]))], 1e9, 0);
//  for (int i = 1; i <= n; i++) g.insert(id[b(calc(bd[i]))], bid[i], 1e9, 0);
//}
int main() {
#ifdef LOCAL
  freopen("input.in", "r", stdin);
#endif
  scanf("%d", &n);
  for (int i = 1; i <= n; i++)
    scanf("%d%d%d", &rd[i].x, &rd[i].y, &rcnt[i]),
        g.insert(S, rid[i] = ++g.tot, rcnt[i], 0);
  for (int i = 1; i <= n; i++)
    scanf("%d%d%d", &bd[i].x, &bd[i].y, &bcnt[i]),
        g.insert(bid[i] = ++g.tot, T, bcnt[i], 0);
  // work([](pair<int, int> a) -> int { return a.x + a.y; });
  // work([](pair<int, int> a) -> int { return a.x - a.y; });
  int ps[] = {++g.tot, ++g.tot, ++g.tot, ++g.tot};
  for (int i = 1; i <= n; i++) {
    g.insert(rid[i], ps[0], 1e9, +rd[i].x + rd[i].y);
    g.insert(rid[i], ps[1], 1e9, +rd[i].x - rd[i].y);
    g.insert(rid[i], ps[2], 1e9, -rd[i].x - rd[i].y);
    g.insert(rid[i], ps[3], 1e9, -rd[i].x + rd[i].y);
    g.insert(ps[0], bid[i], 1e9, -bd[i].x - bd[i].y);
    g.insert(ps[1], bid[i], 1e9, -bd[i].x + bd[i].y);
    g.insert(ps[2], bid[i], 1e9, +bd[i].x + bd[i].y);
    g.insert(ps[3], bid[i], 1e9, +bd[i].x - bd[i].y);
  }
  debug("orz");
  printf("%lld\n", -g.mcmf(S, T, 1e18).second);
  return 0;
}


模拟费用流

// ubsan: undefined
// accoders
#include <algorithm>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <queue>
#include <tuple>
#include <vector>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define debug(...) void(0)
#endif
typedef long long LL;
typedef priority_queue<pair<LL, int>> pqueue;
int n;
LL w[2][100010][4];
pqueue qf[2][4][4], qm[2][4];
// qf 正在等待反悔,原来是 u 可以返回成 v
// qm 是等待匹配这个颜色
vector<int> path[4][4];
LL dis[4][4];
bool typ[4][4];  // typ[u][v] 表示 u->v 这条边是通过左边还有右边过去的
int mch[2][100010];  // mch[u] 表示匹配了谁
bool check(int d, int u, pqueue &q) {
  while (!q.empty() && mch[d][q.top().second] != u) q.pop();
  return !q.empty();
}
void updmch(int d, int p) {
  if (d == 0) {
    int x = p, u = mch[0][x];
    for (int c = 0; c < 4; c++)
      if (u != c) qf[0][u][c].emplace(w[0][x][c] - w[0][x][u], x);
  } else {
    int y = p, v = mch[1][y];
    for (int c = 0; c < 4; c++)
      if (c != v) qf[1][c][v].emplace(w[1][y][c] - w[1][y][v], y);
  }
}
LL flow() {
  for (int u = 0; u < 4; u++)
    for (int v = 0; v < 4; v++)
      if (u != v) {
        dis[u][v] = -1e18;
        path[u][v] = {u, v};
        for (int d : {0, 1})
          if (check(d, d ? v : u, qf[d][u][v]))
            if (qf[d][u][v].top().first > dis[u][v]) {
              dis[u][v] = qf[d][u][v].top().first;
              typ[u][v] = d;
            }
      } else {
        dis[u][v] = 0;
        path[u][v] = {u};
      }
  for (int k = 0; k < 4; k++)
    for (int u = 0; u < 4; u++)
      for (int v = 0; v < 4; v++)
        if (dis[u][v] < dis[u][k] + dis[k][v]) {
          dis[u][v] = dis[u][k] + dis[k][v];
          path[u][v] = path[u][k];
          path[u][v].pop_back();
          path[u][v].insert(path[u][v].end(), path[k][v].begin(),
                            path[k][v].end());
        }
  LL maxf = -1e18;
  int au = 0, av = 0;
  for (int u = 0; u < 4; u++)
    for (int v = 0; v < 4; v++)
      if (check(0, -1, qm[0][u]) && check(1, -1, qm[1][v])) {
        LL left = qm[0][u].top().first, right = qm[1][v].top().first;
        debug("qm[0][%d] = %lld, qm[1][%d] = %lld, dis[%d][%d] = %lld\n", u,
              left, v, right, u, v, dis[u][v]);
        if (maxf < left + right + dis[u][v]) {
          maxf = left + right + dis[u][v];
          tie(au, av) = make_pair(u, v);
        }
      }
#ifdef LOCAL
  debug("path = ");
  for (int x : path[au][av]) debug("%d, ", x);
  debug("\n");
#endif
  if (au != av) {
    for (int i = 0; i + 1 < path[au][av].size(); i++) {
      int u = path[au][av][i], v = path[au][av][i + 1];
      if (!typ[u][v]) {
        int x = qf[0][u][v].top().second;
        assert(mch[0][x] == u);
        mch[0][x] = v;
        updmch(0, x);
      } else {
        int y = qf[1][u][v].top().second;
        assert(mch[1][y] == v);
        mch[1][y] = u;
        updmch(1, y);
      }
    }
  }
  debug("maxf = %lld\n", maxf);
  int x = qm[0][au].top().second;
  mch[0][x] = au;
  updmch(0, x);
  int y = qm[1][av].top().second;
  mch[1][y] = av;
  updmch(1, y);
  return maxf;
}
int main() {
#ifndef NF
  freopen("match.in", "r", stdin);
  freopen("match.out", "w", stdout);
#endif
  scanf("%d", &n);
  for (int i = 1; i <= n; i++) {
    LL x, y;
    scanf("%lld%lld", &x, &y);
    for (int p : {0, 1})
      for (int q : {0, 1})
        w[0][i][p * 2 + q] = x * (p ? -1 : 1) + y * (q ? -1 : 1);
  }
  for (int i = 1; i <= n; i++) {
    LL x, y;
    scanf("%lld%lld", &x, &y);
    for (int p : {0, 1})
      for (int q : {0, 1})
        w[1][i][!p * 2 + !q] = x * (p ? -1 : 1) + y * (q ? -1 : 1);
  }
  memset(mch, -1, sizeof mch);
  for (int i = 1; i <= n; i++) {
    for (int j = 0; j < 4; j++) {
      qm[0][j].emplace(w[0][i][j], i);
      qm[1][j].emplace(w[1][i][j], i);
    }
  }
  LL ans = 0;
  for (int i = 1; i <= n; i++) ans += flow();
  printf("%lld\n", ans);
  return 0;
}

应该有一篇感谢文章的,我找不到在哪里了。

posted @ 2023-10-30 15:54  caijianhong  阅读(20)  评论(0编辑  收藏  举报