BZOJ3571 [Hnoi2014]画框 【分治 + KM算法】

题目链接

BZOJ3571

题解

如果知道最小乘积生成树,那么这种双权值乘积最小就是裸题了
将两权值和作为坐标,转化为二维坐标系下凸包上的点,然后不断划分分治就好了

这里求的是最小匹配值,每次找点套一个二分图最小权匹配
为什么用KM算法?因为这道题丧心病狂卡费用流QAQ

写完就A啦,十分的感人

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define LL long long int
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define BUG(s,n) for (int i = 1; i <= (n); i++) cout<<s[i]<<' '; puts("");
using namespace std;
const int maxn = 75,maxm = 100005,INF = 1000000000;
inline int read(){
    int out = 0,flag = 1; char c = getchar();
    while (c < 48 || c > 57){if (c == '-') flag = -1; c = getchar();}
    while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();}
    return out * flag;
}
int n,X[maxn][maxn],Y[maxn][maxn];
int w[maxn][maxn],expa[maxn],expb[maxn],cp[maxn],visa[maxn],visb[maxn],dl[maxn];
struct point{int x,y;}ans;
inline point  operator -(const point& a,const point& b){
    return (point){a.x - b.x,a.y - b.y};
}
inline LL operator *(const point& a,const point& b){
    return 1ll * a.x * b.y - 1ll * a.y * b.x;
}
inline bool operator <(const point& a,const point& b){
    return 1ll * a.x * a.y == 1ll * b.x * b.y ? a.x < b.x : 1ll * a.x * a.y < 1ll * b.x * b.y;
}
bool dfs(int u){
    visa[u] = true;
    REP(i,n) if (!visb[i]){
        int kl = expa[u] + expb[i] - w[u][i];
        if (!kl){
            visb[i] = true;
            if (!cp[i] || dfs(cp[i])) {cp[i] = u; return true;}
        }
        else dl[i] = min(dl[i],kl);
    }
    return false;
}
point km(){
    REP(i,n) expa[i] = -INF,expb[i] = cp[i] = 0;
    REP(i,n) REP(j,n) expa[i] = max(expa[i],w[i][j]);
    REP(i,n){
        REP(j,n) dl[j] = INF;
        while (true){
            REP(j,n) visa[j] = visb[j] = false;
            if (dfs(i)) break;
            int kl = INF;
            REP(j,n) if (!visb[j]) kl = min(kl,dl[j]);
            REP(j,n){
                if (visa[j]) expa[j] -= kl;
                if (visb[j]) expb[j] += kl;
                    else dl[j] -= kl;
            }
        }
    }
    point re; re.x = re.y = 0;
    REP(i,n) re.x += X[cp[i]][i],re.y += Y[cp[i]][i];
    if (re < ans) ans = re;
    return re;
}
void solve(point A,point B){
    REP(i,n) REP(j,n) w[i][j] = -((A.y - B.y) * X[i][j] + (B.x - A.x) * Y[i][j]);
    point C = km();
    if ((C - A) * (B - A) <= 0) return;
    solve(A,C); solve(C,B);
}
int main(){
    int T = read();
    while (T--){
        n = read(); ans.x = INF; ans.y = INF;
        REP(i,n) REP(j,n) X[i][j] = read();
        REP(i,n) REP(j,n) Y[i][j] = read();
        REP(i,n) REP(j,n) w[i][j] = -X[i][j];
        point A = km();
        REP(i,n) REP(j,n) w[i][j] = -Y[i][j];
        point B = km();
        solve(A,B);
        printf("%d\n",ans.x * ans.y);
    }
    return 0;
}

转载于:https://www.cnblogs.com/Mychael/p/8995121.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值