洛谷P3236,[HNOI2014]画框,巧妙运用叉积+最小乘积生成树

正题

      题目戳这里

      给出两个矩阵,定义要求构造一个排列P,使得P\in [1,n] && 而且每个数只出现一次。使得

      令\\Asum=\sum_{i=1}^n A_{i,P_i} \\Bsum=\sum_{i=1}^n B_{i,P_i} \\Asum*Bsum最大。

      如果我们只有一个矩阵怎么做??

      很明显就是直接二分图最优匹配(你看,博主又在不要脸地推广自己的博客)

      但是现在是二维的。

      相当于我们以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,使得\underset{AC}{\rightarrow}\times \underset{AB}{\rightarrow}最大就行了。

      A和B点的坐标都可以用二分图最优匹配算出来。

      那么未知的是C点的坐标。

      \\\underset{AC}{\rightarrow}\times \underset{AB}{\rightarrow}=(Asum_C-Asum_A,Bsum_C-Bsum_A)\times(Asum_B-Asum_A,Bsum_B-Bsum_A) \\=(Asum_C-Asum_A)\times(Bsum_B-Bsum_A)-(Bsum_C-Bsum_A)\times(Asum_B-Asum_A) \\=Asum_C(Bsum_B-Bsum_A)+Bsum_C(Asum_A-Asum_B)+O

      其实就是叉积的推导,比较难看,自己想一想。

      其中O写出来是一个常数,所以我们不用理它。

      而未知的Asum_C和未知的Bsum_C使我们要求的。

      所以我们二分图最优匹配i到j的权值应该是A_{i,j}(Bsum_B-Bsum_A)+B_{i,j}(Asum_A-Asum_B)

      因为这样加起来就是Asum_CBsum_C了。

      最优匹配跑出来的是最大权值,正好符合我们的要求。

      那么不断的分治,找出下凸包的所有点,回溯的时候看一下哪一个小就好了。

// 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));
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值