bzoj3571: [Hnoi2014]画框 最小乘积匹配+最小乘积XX总结,

时间:2023-03-09 01:43:30
bzoj3571: [Hnoi2014]画框 最小乘积匹配+最小乘积XX总结,

思路大概同bzoj2395(传送门:http://www.cnblogs.com/DUXT/p/5739864.html),还是将每一种匹配方案的Σai看成x,Σbi看成y,然后将每种方案转化为平面上的点,再用km去找最远的点就行了。

然而几个月前就学过km且到现在还未写过一道km的题的我并不知道km如何对于负权给出最优解。。。。

#define XX 某传统算法(例如:最小生成树,二分图最优带权匹配什么的)

顺便总结一下最小乘积XX

即对于XX引入两个权值的概念(或是多个权值,一般是两个),看似无从下手,却可以将每一组可行解的方案的两个sum转化为平面内一个点,然后就可以发现一个十分优美的性质即最优解一定在凸包上,于是可以利用一种类似于快包算法的算法,依旧是用XX找出一定在凸包上的两个点,然后再次利用XX进行分治,递归地下去找,弄清楚边界条件(一般一个叉乘就可以轻松搞定),这样问题就被轻易地解决了。(貌似知道了这样的一个模板,基本所有类似问题都可以得到解决)

 #include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define maxn 100
#define inf 100000000 int cases,n;
int a[maxn][maxn],b[maxn][maxn],val[maxn][maxn],slack[maxn],valx[maxn],valy[maxn],linky[maxn];
bool visx[maxn],visy[maxn]; struct point{
int x,y;
}ans; point operator -(point a,point b){return(point){a.x-b.x,a.y-b.y};}
double operator *(point a,point b){return a.x*b.y-a.y*b.x;} bool find(int x){
visx[x]=;
for (int y=;y<=n;y++)
if (!visy[y]){
int t=valx[x]+valy[y]-val[x][y];
if (!t){
visy[y]=;
if (!linky[y]||find(linky[y])){
linky[y]=x;
return ;
}
}
else slack[y]=min(slack[y],t);
}
return ;
} point km(){
memset(valx,,sizeof(valx));
memset(valy,,sizeof(valy));
memset(linky,,sizeof(linky));
for (int i=;i<=n;i++)
for (int j=;j<=n;j++)
valx[i]=max(valx[i],val[i][j]);
for (int x=;x<=n;x++){
memset(slack,,sizeof(slack));
while (){
memset(visx,,sizeof(visx));
memset(visy,,sizeof(visy));
if (find(x)) break;
int d=inf;
for (int i=;i<=n;i++) if (!visy[i]) d=min(d,slack[i]);
for (int i=;i<=n;i++) if (visx[i]) valx[i]-=d;
for (int i=;i<=n;i++) if (visy[i]) valy[i]+=d;
}
}
point now={,};
for (int i=;i<=n;i++) now.x+=a[linky[i]][i],now.y+=b[linky[i]][i];
if ((ans.x==inf&&ans.y==inf)||(ans.x*ans.y>now.x*now.y)) ans=now;
return now;
} bool operator ==(point a,point b){return a.x==b.x&&a.y==b.y;} void solve(point x,point y){
for (int i=;i<=n;i++)
for (int j=;j<=n;j++)
val[i][j]=b[i][j]*(x.x-y.x)+a[i][j]*(y.y-x.y);
point z=km();
if ((z-x)*(y-z)<=) return;
solve(x,z);
solve(z,y);
} int main(){
scanf("%d",&cases);
while (cases--){
scanf("%d",&n);
ans.x=ans.y=inf;
for (int i=;i<=n;i++)
for (int j=;j<=n;j++) scanf("%d",&a[i][j]);
for (int i=;i<=n;i++)
for (int j=;j<=n;j++) scanf("%d",&b[i][j]);
point minx,miny;
for (int i=;i<=n;i++)
for (int j=;j<=n;j++) val[i][j]=-a[i][j];
minx=km();
for (int i=;i<=n;i++)
for (int j=;j<=n;j++) val[i][j]=-b[i][j];
miny=km();
solve(minx,miny);
printf("%d\n",ans.x*ans.y);
}
return ;
}