题解 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
考虑费用流,因为我们观察到如下事实:
也就是说这个曼哈顿距离可以转化为四种情况取 \(\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;
}
应该有一篇感谢文章的,我找不到在哪里了。
本文来自博客园,作者:caijianhong,转载请注明原文链接:https://www.cnblogs.com/caijianhong/p/solution-AGC034D.html