[BZOJ3571][Hnoi2014]画框(最小乘积匹配裸题)

本文介绍了一种基于最大匹配原理的优化算法,通过构建点(x,y)来表示匹配方案,并利用KM算法和费用流方法找到最优解。算法通过不断寻找下凸壳上的关键点并进行分治来提高效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

与最小乘积生成树相似。
把一种可行匹配看作一个点 (x,y)(x,y)xx 表示该匹配方案中所有边的 A 之和, yy 表示该匹配方案中所有边的 B 之和。
显然,最优解一定在所有点的下凸壳上。
Ai,j,Bi,j200Ai,j,Bi,j≤200 得到,虽然点数(匹配方案数)是 n!n! 的,但下凸壳上的点数是有限的。
和最小乘积生成树一样,先用 KM / 费用流 找到一个横坐标最小的点 A(ax,ay)A(ax,ay) ,一个纵坐标最小的点 B(bx,by)B(bx,by) 。则最优解一定是下凸壳上 A,BA,B 两点之间的所有点。
一样用分治的算法。即找到一个下凸壳上的点 CC ,最大化 CABAB 的距离。
也就是最大化 AC×ABAC→×AB→ 的值。
考虑暴力一点,直接把叉积的式子拆开:

(cxax)×(byay)(cyay)×(bxax)(cx−ax)×(by−ay)−(cy−ay)×(bx−ax)

cx×bycx×ayax×by+ax×aycy×bx+cy×ax+ay×bxay×axcx×by−cx×ay−ax×by+ax×ay−cy×bx+cy×ax+ay×bx−ay×ax

cx×(byay)+cy×(axbx)+ax×(ayby)+ay×(bxax)cx×(by−ay)+cy×(ax−bx)+ax×(ay−by)+ay×(bx−ax)

ax×(ayby)+ay×(bxax)ax×(ay−by)+ay×(bx−ax) 是和 CC 无关的东西,可以忽略。
所以就是要使 cx×(byay)+cy×(axbx) 最大化。
于是可以通过 KM / 费用流 找到点 CC
然后往 ACCBCB 递归下去,直到找不到一个在 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值