KM+最小乘积生成树
#include<cstdio>
#include<algorithm>
#define INF 0x7fffffff
#define NEG_INF 0x80000000
using namespace std;
const int M=75;
int A[M][M],B[M][M],W[M][M];
int left[M],Lx[M],Ly[M],slack[M],n;
bool S[M],T[M];
bool match(int x){
S[x]=1;
for(int i=1;i<=n;++i){
if(T[i])continue;
int delta=Lx[x]+Ly[i]-W[x][i];
if(!delta){
T[i]=1;
if(!left[i]||match(left[i])){
left[i]=x;
return true;
}
}
else slack[i]=min(slack[i],delta);
}
return false;
}
void update(){
int delta=INF;
for(int i=1;i<=n;i++)
if(!T[i])delta=min(delta,slack[i]);
for(int i=1;i<=n;i++){
if(S[i])Lx[i]-=delta;
if(T[i])Ly[i]+=delta;
else slack[i]-=delta;
}
}
void KM(){
for(int i=1;i<=n;i++){
Lx[i]=NEG_INF;Ly[i]=left[i]=0;
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;
for(;;){
for(int j=1;j<=n;j++)
S[j]=T[j]=0;
if(match(i))break;
else update();
}
}
}
struct Point{int x,y;};
int ans;
void merge(Point a,Point b){
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
W[i][j]=(b.y-a.y)*A[i][j]+(a.x-b.x)*B[i][j];
KM();
Point c;
c.x=c.y=0;
for(int i=1;i<=n;i++)
c.x+=A[left[i]][i],
c.y+=B[left[i]][i];
// printf("Point %d %d\n",c.x,c.y);
if((c.x==a.x&&c.y==a.y)||(c.x==b.x&&c.y==b.y))return;
ans=min(ans,c.x*c.y);
merge(a,c);merge(c,b);
}
void solve(){
Point a,b;ans=INF;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
W[i][j]=-A[i][j];
a.x=a.y=0;KM();
for(int i=1;i<=n;++i)
a.x+=A[left[i]][i],
a.y+=B[left[i]][i];
ans=min(ans,a.x*a.y);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
W[i][j]=-B[i][j];
b.x=b.y=0;KM();
for(int i=1;i<=n;++i)
b.x+=A[left[i]][i],
b.y+=B[left[i]][i];
// printf("A %d %d\n",a.x,a.y);
// printf("B %d %d\n",b.x,b.y);
ans=min(ans,b.x*b.y);
merge(a,b);
printf("%d\n",ans);
}
int main(){
int Cas;
scanf("%d",&Cas);
while(Cas--){
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&A[i][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&B[i][j]);
solve();
}
return 0;
}