这个算是常见套路题,记得暑假里做过BZOJ2395 time is money,然后发现这两个是一个套路的
首先我们考虑将两维单独考虑,令(x=sum_{i=1}^n a_{i,p_i},y=sum_{i=1}^n b_{i,p_i}),那么我们可以把一种匹配方式看作平面上的一个点((x,y))
考虑最后我们所求的答案(k=xy),即(y=frac{k}{x}),为反比例函数,那么显然我们要找的点肯定在靠近原点的下凸壳上
既然这样,我们不妨找出位于凸壳上的两点(A,B),(A)满足(sum_{i=1}^n a_{i,p_i})最小,(B)满足(sum_{i=1}^n b_{i,p_i})最小
然后我们作出线段(AB),考虑在靠近原点的一侧找一个点(C),使得这个点离原点尽量近
那么此时显然(C)满足(S_{ riangle ABC} max),换句话说就是(vec{AB} imes vec{AC}min)(因为是负的)
考虑我们展开它:
[vec{AB} imes vec{AC}\=(B_x-A_x) imes(C_y-A_y)-(B_y-A_y) imes (C_x-A_x)\=(A_y-B_y) imes C_x+(B_x-A_x) imes C_y+cdots
]
其中(cdots)都是只与(A,B)有关的项,因此我们再找出满足((A_y-B_y) imes C_x+(B_x-A_x) imes C_y)最小的点(C)
可是(C)也不一定是最优的啊,没事我们发现此时我们又有了两条线段(AC,CB),直接递归下去做就好了
边界显然是找不到这样的(C)点,即(vec{AB} imes vec{AC}ge 0)
然后找到这样的点的过程显然就是个最大权匹配,直接上KM即可,复杂度(O( ext{很快}))
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=150,INF=1e9;
struct point
{
int x,y;
inline point(CI X=0,CI Y=0) { x=X; y=Y; }
}; int t,n,w[N][N],a[N][N],b[N][N],ans;
inline point operator - (const point& A,const point& B)
{
return point(A.x-B.x,A.y-B.y);
}
inline int Cross(const point& A,const point& B)
{
return A.x*B.y-A.y*B.x;
}
namespace KM
{
int dx[N],dy[N],rsd[N],fr[N]; bool vx[N],vy[N];
inline void build(CI rx,CI ry)
{
for (RI i=1,j;i<=n;++i) for (j=1;j<=n;++j)
w[i][j]=-(rx*a[i][j]+ry*b[i][j]);
}
inline bool find(CI now)
{
vx[now]=1; for (RI to=1;to<=n;++to) if (!vy[to])
{
int dlt=dx[now]+dy[to]-w[now][to];
if (!dlt) { vy[to]=1; if (!~fr[to]||find(fr[to])) return fr[to]=now,1; }
else rsd[to]=min(rsd[to],dlt);
}
return 0;
}
inline point KM(void)
{
RI i,j; for (i=1;i<=n;++i) fr[i]=-1,dx[i]=-INF,dy[i]=0;
for (i=1;i<=n;++i) for (j=1;j<=n;++j) dx[i]=max(dx[i],w[i][j]);
for (i=1;i<=n;++i)
{
for (j=1;j<=n;++j) rsd[j]=INF; for (;;)
{
for (j=1;j<=n;++j) vx[j]=vy[j]=0; if (find(i)) break;
int dlt=INF; for (j=1;j<=n;++j) if (!vy[j]) dlt=min(dlt,rsd[j]);
for (j=1;j<=n;++j) if (vx[j]) dx[j]-=dlt;
for (j=1;j<=n;++j) if (vy[j]) dy[j]+=dlt; else rsd[j]-=dlt;
}
}
point ret; for (i=1;i<=n;++i) ret.x+=a[fr[i]][i],ret.y+=b[fr[i]][i]; return ret;
}
};
inline void solve(const point& A,const point& B)
{
KM::build(A.y-B.y,B.x-A.x); point C=KM::KM(); ans=min(ans,C.x*C.y);
if (Cross(B-A,C-A)>=0) return; solve(A,C); solve(C,B);
}
int main()
{
for (scanf("%d",&t);t;--t)
{
RI i,j; for (scanf("%d",&n),i=1;i<=n;++i) for (j=1;j<=n;++j)
scanf("%d",&a[i][j]); for (i=1;i<=n;++i) for (j=1;j<=n;++j)
scanf("%d",&b[i][j]); point A,B;
KM::build(1,0); A=KM::KM(); KM::build(0,1); B=KM::KM();
ans=min(A.x*A.y,B.x*B.y); solve(A,B); printf("%d
",ans);
}
return 0;
}