[BZOJ3571][HNOI2014]画框

bzoj
luogu

description

给你两个矩阵\(A_{i,j}\)\(B_{i,j}\),你需要求一个排列\(p_i\),最小化$$\sum_{i=1}^nA_{i,p_i}\times \sum_{i=1}^nB_{i,p_i}$$
\(n\le70\)

sol

最小乘积\(KM\)
运用数形结合的思想,令\(X=\sum_{i=1}^nA_{i,p_i},Y=\sum_{i=1}^nB_{i,p_i}\),那么每一个排列就对应了笛卡尔坐标系中的一个点。
因为我们要最小化\(XY\),所以我们希望所求排列对应的点尽可能接近原点。
先分别以\(A_{i,j}\)\(B_{i,j}\)作为权值做二分图最小权匹配求出横坐标/纵坐标最小的点,设为\(A,B\)
我们希望能够找到一个点\(C\)使答案更优。
这个\(C\)应该尽量靠近坐标原点,或者说,应该尽量离\(\vec{AB}\)尽量远。
等同于是要求一个点\(C\),最小化\(\vec{AB} \times \vec{AC}\)
那么我们就可以以\((B_x-A_x)\times B_{i,j}+(A_y-B_y)\times A_{i,j}\)作为新的权值做最小权匹配,这样就可以求出\(C\)了。
求出\(C\)以后,分别递归处理\(A,C\)\(C,B\)。递归的边界就是\(\vec{AB} \cdot\vec{AC}=0\)
因为是最小权匹配所以跑\(KM\)的时候边权要取反。

code

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
	int x=0,w=1;char ch=getchar();
	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
	if (ch=='-') w=0,ch=getchar();
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return w?x:-x;
}
const int N = 100;
const int inf = 1e9;
struct point{
	int x,y;
	point(){x=y=0;}
	point(int _x,int _y){x=_x,y=_y;}
	point operator - (point b)
		{return point(x-b.x,y-b.y);}
	bool operator < (point b)
		{return x*y<b.x*b.y;}
	int operator * (point b)
		{return x*b.y-y*b.x;}
}ans;
int n,a[N][N],b[N][N],val[N][N],mat[N],lv[N],rv[N],slack[N],vis[N],pre[N];
void aug(int s){
    for (int i=0;i<=n;++i) slack[i]=inf,vis[i]=pre[i]=0;
    int u=0;mat[u]=s;
    do{
        int now=mat[u],d=inf,nxt;vis[u]=1;
        for (int v=1;v<=n;++v)
            if (!vis[v]){
                if (lv[now]+rv[v]-val[now][v]<slack[v])
                    slack[v]=lv[now]+rv[v]-val[now][v],pre[v]=u;
                if (d>slack[v]) d=slack[v],nxt=v;
            }
        for (int i=0;i<=n;++i)
            if (vis[i]) lv[mat[i]]-=d,rv[i]+=d;
            else slack[i]-=d;
        u=nxt;
    }while (mat[u]);
    while (u) mat[u]=mat[pre[u]],u=pre[u];
}
point KM(){
	memset(mat,0,sizeof(mat));
	for (int i=1;i<=n;++i){
		lv[i]=-inf;rv[i]=0;
		for (int j=1;j<=n;++j)
			lv[i]=max(lv[i],val[i][j]);
	}
	for (int i=1;i<=n;++i) aug(i);
	point res=point(0,0);
	for (int i=1;i<=n;++i)
		res.x+=a[mat[i]][i],res.y+=b[mat[i]][i];
	if (res<ans) ans=res;return res;
}
void solve(point A,point B){
	for (int i=1;i<=n;++i)
		for (int j=1;j<=n;++j)
			val[i][j]=-(B.x-A.x)*b[i][j]+(B.y-A.y)*a[i][j];
	point C=KM();
	if (A.x>B.x) exit(1);
	if ((B-A)*(C-A)>=0) return;
	solve(A,C);solve(C,B);
}
int main(){
	int Case=gi();while (Case--){
		n=gi();ans=point(40000,40000);
		for (int i=1;i<=n;++i)
			for (int j=1;j<=n;++j)
				a[i][j]=gi();
		for (int i=1;i<=n;++i)
			for (int j=1;j<=n;++j)
				b[i][j]=gi();
		for (int i=1;i<=n;++i)
			for (int j=1;j<=n;++j)
				val[i][j]=-a[i][j];
		point ma=KM();
		for (int i=1;i<=n;++i)
			for (int j=1;j<=n;++j)
				val[i][j]=-b[i][j];
		point mb=KM();
		solve(ma,mb);
		printf("%d\n",ans.x*ans.y);
	}
	return 0;
}
posted @ 2018-08-09 22:24  租酥雨  阅读(201)  评论(0编辑  收藏  举报