与最小乘积生成树相似。
把一种可行匹配看作一个点 (x,y)(x,y) , xx 表示该匹配方案中所有边的 之和, yy 表示该匹配方案中所有边的 之和。
显然,最优解一定在所有点的下凸壳上。
由 Ai,j,Bi,j≤200Ai,j,Bi,j≤200 得到,虽然点数(匹配方案数)是 n!n! 的,但下凸壳上的点数是有限的。
和最小乘积生成树一样,先用 KM / 费用流 找到一个横坐标最小的点 A(ax,ay)A(ax,ay) ,一个纵坐标最小的点 B(bx,by)B(bx,by) 。则最优解一定是下凸壳上 A,BA,B 两点之间的所有点。
一样用分治的算法。即找到一个下凸壳上的点 CC ,最大化 到 ABAB 的距离。
也就是最大化 AC→×AB→AC→×AB→ 的值。
考虑暴力一点,直接把叉积的式子拆开:
(cx−ax)×(by−ay)−(cy−ay)×(bx−ax)(cx−ax)×(by−ay)−(cy−ay)×(bx−ax)
cx×by−cx×ay−ax×by+ax×ay−cy×bx+cy×ax+ay×bx−ay×axcx×by−cx×ay−ax×by+ax×ay−cy×bx+cy×ax+ay×bx−ay×ax
cx×(by−ay)+cy×(ax−bx)+ax×(ay−by)+ay×(bx−ax)cx×(by−ay)+cy×(ax−bx)+ax×(ay−by)+ay×(bx−ax)
ax×(ay−by)+ay×(bx−ax)ax×(ay−by)+ay×(bx−ax) 是和 CC 无关的东西,可以忽略。
所以就是要使 最大化。
于是可以通过 KM / 费用流 找到点 CC 。
然后往 和 CBCB 递归下去,直到找不到一个在 ABAB 左下角的点 CC 为止。
代码:
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
using namespace std;
inline int read() {
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
const int N = 75, INF = 0x3f3f3f3f;
int n, a[N][N], b[N][N], val[N][N], tox[N], toy[N], ex[N], ey[N],
mind, orz[N];
bool visx[N], visy[N];
bool dfs(int u) {
int v; visx[u] = 1; For (v, 1, n) {
if (visy[v]) continue; int tmp = ex[u] + ey[v] - val[u][v];
if (tmp == 0) {
visy[v] = 1; if (!toy[v] || dfs(toy[v])) {
toy[v] = u; tox[u] = v; return 1;
}
}
else orz[v] = min(orz[v], tmp);
}
return 0;
}
void KM() {
int i, j; For (i, 1, n) tox[i] = toy[i] = 0;
For (i, 1, n) {
ex[i] = val[i][1]; ey[i] = 0;
For (j, 2, n) ex[i] = max(ex[i], val[i][j]);
}
For (i, 1, n) {
For (j, 1, n) orz[j] = INF;
while (1) {
For (j, 1, n) visx[j] = visy[j] = 0;
mind = INF; if (dfs(i)) break;
For (j, 1, n) if (!visy[j]) mind = min(mind, orz[j]);
For (j, 1, n) {
if (visx[j]) ex[j] -= mind;
if (visy[j]) ey[j] += mind;
}
}
}
}
int solve(int ax, int ay, int bx, int by) {
int i, j, cx = 0, cy = 0; For (i, 1, n) For (j, 1, n)
val[i][j] = a[i][j] * (by - ay) + b[i][j] * (ax - bx); KM();
For (i, 1, n) cx += a[i][tox[i]], cy += b[i][tox[i]];
if ((ax == cx && ay == cy) || (bx == cx && by == cy))
return min(ax * ay, bx * by);
return min(solve(ax, ay, cx, cy), solve(cx, cy, bx, by));
}
void work() {
int i, j, ax = 0, ay = 0, bx = 0, by = 0; n = read();
For (i, 1, n) For (j, 1, n) a[i][j] = read();
For (i, 1, n) For (j, 1, n) b[i][j] = read();
For (i, 1, n) For (j, 1, n) val[i][j] = -a[i][j]; KM();
For (i, 1, n) ax += a[i][tox[i]], ay += b[i][tox[i]];
For (i, 1, n) For (j, 1, n) val[i][j] = -b[i][j]; KM();
For (i, 1, n) bx += a[i][tox[i]], by += b[i][tox[i]];
printf("%d\n", solve(ax, ay, bx, by));
}
int main() {
int T = read(); while (T--) work();
return 0;
}