题解 AGC034D【Manhattan Max Matching】

发布时间 2023-10-30 15:56:40作者: caijianhong

题解 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 <cstdio>
#include <cstring>
#include <algorithm>
#include <utility>
#include <queue>
#include <functional>
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],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;
}