【BZOJ 3571】 [Hnoi2014]画框

3571: [Hnoi2014]画框

Time Limit: 20 Sec Memory Limit: 128 MB
Submit: 513 Solved: 292
[Submit][Status][Discuss]
Description

小T准备在家里摆放几幅画,为此他买来了N幅画和N个画框。为了体现他的品味,小T希望能合理地搭配画与画框,使得其显得既不过于平庸也不太违和。对于第 幅画与第 个画框的配对,小T都给出了这个配对的平凡度Aij 与违和度Bij 。整个搭配方案的总体不和谐度为每对画与画框平凡度之和与每对画与画框违和度的乘积。具体来说,设搭配方案中第i幅画与第Pi个画框配对,则总体不和谐度为

小T希望知道通过搭配能得到的最小的总体不和谐度是多少。

Input

输入文件第 行是一个正整数T ,表示数据组数,接下来是T组数据。
对于每组数据,第 行是一个正整数N,表示有N对画和画框。
第2到第N+1行,每行有N个非负整数,第i+1 行第j个数表示Aij 。
第N+2到第2*N+1行,每行有N个非负整数,第i+N+1 行第j个数表示Bij 。

Output

包含T行,每行一个整数,表示最小的总体不和谐度

Sample Input

1

3

4 3 2

2 3 4

3 2 1

2 3 2

2 2 4

1 1 3

Sample Output

30
HINT

第1幅画搭配第3个画框,第2幅画搭配第1个画框,第3 幅画搭配第2个画框,则总体不和谐度为30

N<=70,T<=3,Aij<=200,Bij<=200

km算法+最小乘积生成树。

看到计算式,可以想到最小乘积生成树(详见【BZOJ 2395】)

那么我们用km算法求出匹配,用最小乘积生成树来做即可。

注意:
用最小乘积生成树的时候要注意一个细节,由于km算法是求最大匹配的,所以计算的时候我们要把边权取相反数,也就是说在分治过程中边权也要取相反数!

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#define M 75
#define inf 1000000000000000LL
#define LL long long
using namespace std;
int vx[M],n,vy[M],match[M];
LL w[M][M],a[M][M],b[M][M],ans=inf;
LL slack[M],lx[M],ly[M];
struct Point
{
    LL x,y;
}A,B;
bool dfs(int x)
{
    vx[x]=1;
    for (int i=1;i<=n;i++)
        if (!vy[i])
        {
            LL t=lx[x]+ly[i]-w[x][i];
            if (!t)
            {
                vy[i]=1;
                if (!match[i]||dfs(match[i]))
                {
                    match[i]=x;
                    return 1;
                }
             }
             else slack[i]=min(slack[i],t);
        }
    return 0;
}
Point KM()
{
    for (int i=1;i<=n;i++)
        match[i]=0,ly[i]=0;
    for (int i=1;i<=n;i++)
    {
        lx[i]=-inf;
        for (int j=1;j<=n;j++)
            lx[i]=max(lx[i],w[i][j]);
    }
    for (int i=1;i<=n;i++)
    {
        for (int j=1;j<=n;j++)
                slack[j]=inf;       
        while(1)
        {
            memset(vx,0,sizeof(vx));
            memset(vy,0,sizeof(vy));
            if (dfs(i)) break;
            LL d=inf;
            for (int j=1;j<=n;j++)
                if (!vy[j]&&slack[j]<d)
                    d=slack[j];
            for (int j=1;j<=n;j++)
                if (vx[j]) lx[j]-=d;
            for (int j=1;j<=n;j++)
                if (vy[j]) ly[j]+=d;
                else slack[j]-=d;
        }
    }
    Point p;
    p.x=0,p.y=0;
    for (int i=1;i<=n;i++)
         p.x+=a[match[i]][i],p.y+=b[match[i]][i];
    ans=min(ans,p.x*p.y);
    return p;
}
LL Cross(Point a,Point b,Point c)
{
    return (b.x-a.x)*(c.y-a.y)-(c.x-a.x)*(b.y-a.y);
}
void Solve(Point A,Point B)
{
    LL y=B.y-A.y,x=A.x-B.x;
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
            w[i][j]=y*a[i][j]+x*b[i][j];
    Point p=KM();
    if (Cross(p,A,B)>=0) return;
    Solve(A,p);
    Solve(p,B);
}
int main()
{
    int T;
    scanf("%d",&T);
    while (T--)
    {
        ans=inf;
        scanf("%d",&n);
        for (int i=1;i<=n;i++)
            for (int j=1;j<=n;j++)
            {
                scanf("%lld",&a[i][j]);
                w[i][j]=-a[i][j];
            }
         for (int i=1;i<=n;i++)
            for (int j=1;j<=n;j++)
                scanf("%lld",&b[i][j]);
        A=KM();
        for (int i=1;i<=n;i++)
            for (int j=1;j<=n;j++)
                w[i][j]=-b[i][j];
        B=KM();
        Solve(A,B);
        printf("%lld\n",ans);
    }
    return 0;
}

你可能感兴趣的:(KM算法,OI,bzoj,最小乘积生成树)