正题
给出两个矩阵,定义要求构造一个排列P,使得 而且每个数只出现一次。使得
令最大。
如果我们只有一个矩阵怎么做??
很明显就是直接二分图最优匹配。(你看,博主又在不要脸地推广自己的博客)
但是现在是二维的。
相当于我们以Asum为横坐标,Bsum为纵坐标,使得xy最小。
但是我们没办法去标点,因为总的点数是很大的,70!
我们继续猜想。
首先,xy最小肯定在这些点组成的下凸包里面。
其次,Asum最小的点和Bsum最小的点,肯定在这个下凸包上。
也就是说,是这个样子的。
、
A点就是我们Asum最小的点,而B就是我们Bsum最小的点。
很明显发现一个性质,下凸包的点都在直线AB的左下方。理解不了的可以用几何画板看一下。
证明:因为以A画一条反比例函数,以B画一条反比例函数。又因为没有谁Asum是比A点小的,以A为原点做笛卡尔坐标系,以B为原点做笛卡尔坐标系,这两个坐标的第一象限都是不能要的,就像下图的CDE区域。
那么考虑的只有ABF和AB以下的区域了。
A和B所在的反比例函数都是下凹的了,反比例函数包着的第一象限区域肯定包括ABF,所以ABF很明显不行。
接着,我们已经证明了答案肯定在A和B两点或者线段AB以下。
又有一个很明显的道理,距离AB直线最远的点肯定在下凸包上。
那么怎么求最远点呢》?
叉积~
因为叉积表示的是向量AB与向量AC所组成的平行四边形的面积。
所以存在一个点C,使得最大就行了。
A和B点的坐标都可以用二分图最优匹配算出来。
那么未知的是C点的坐标。
其实就是叉积的推导,比较难看,自己想一想。
其中O写出来是一个常数,所以我们不用理它。
而未知的Asum_C和未知的Bsum_C使我们要求的。
所以我们二分图最优匹配i到j的权值应该是
因为这样加起来就是和了。
最优匹配跑出来的是最大权值,正好符合我们的要求。
那么不断的分治,找出下凸包的所有点,回溯的时候看一下哪一个小就好了。
// luogu-judger-enable-o2
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;
struct node{
int x,y;
bool operator ==(const node q)const{
return x==q.x && y==q.y;
}
};
int T,n;
int a[75][75],b[75][75];
int g[75][75];
int tx[75],ty[75];
bool visx[75],visy[75];
int prep[75];
bool find(int x){
visx[x]=true;
for(int i=1;i<=n;i++)
if(!visy[i] && tx[x]+ty[i]==g[x][i]){
visy[i]=true;
if(prep[i]==0 || find(prep[i])){
prep[i]=x;
return true;
}
}
return false;
}
node KM(){
memset(tx,0,sizeof(tx));
memset(ty,0,sizeof(ty));
memset(prep,0,sizeof(prep));
int mmin=1e9;
for(int i=1;i<=n;i++){
while(1){
memset(visx,false,sizeof(visx));
memset(visy,false,sizeof(visy));
if(find(i)) break;
mmin=1e9;
for(int j=1;j<=n;j++) if(visx[j])
for(int k=1;k<=n;k++) if(!visy[k]) mmin=min(mmin,tx[j]+ty[k]-g[j][k]);
for(int j=1;j<=n;j++) if(visx[j]) tx[j]-=mmin;
for(int j=1;j<=n;j++) if(visy[j]) ty[j]+=mmin;
}
}
node op=(node){0,0};
for(int i=1;i<=n;i++) op.x+=a[prep[i]][i],op.y+=b[prep[i]][i];
return op;
}
int solve(node left,node right){
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
g[i][j]=(right.y-left.y)*a[i][j]+(left.x-right.x)*b[i][j];
node mid=KM();
if(left==mid || mid==right) return min(left.x*left.y,right.x*right.y);
return min(solve(left,mid),solve(mid,right));
}
int main(){
scanf("%d",&T);
node left,right;
while(T--){
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]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
g[i][j]=-a[i][j];
left=KM();
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
g[i][j]=-b[i][j];
right=KM();
printf("%d\n",solve(left,right));
}
}