首页 > 代码库 > BZOJ 3571 [Hnoi2014]画框(最小乘积完美匹配)
BZOJ 3571 [Hnoi2014]画框(最小乘积完美匹配)
【题目链接】 http://www.lydsy.com/JudgeOnline/problem.php?id=3571
【题目大意】
给出一张二分图,每条边上有a,b两个值,求完美匹配,
使得suma*sumb最小。
【题解】
把方案看成一个二维点,x=sum(a),y=sum(b)
答案一定在下凸壳上,找到l,r两个点,l是x最小的,r是y最小的
然后递归调用work(l,r):找到离该直线最远的点,那个点一定在下凸壳上
将边权设为(a,b)叉积(l-r),求出最小完美匹配就是那个点mid
因为叉积计算的时候包含符号,(suma,sumb)与直线的叉积最小就是三角形的面积最大,
因而就是最远点,总和的叉积最小等价于叉积最小完美匹配。
然后递归work(l,mid),work(mid,r)
就能够枚举下凸壳上所有的点了。
【代码】
#include <cstdio>#include <algorithm>#include <cstring>using namespace std;typedef long long LL;const int N=310;const LL INF=0x3f3f3f3f3f3f3f3f;int nx,ny; //两边的点数 LL g[N][N]; //二分图描述int linker[N];//y中各点匹配状态LL lx[N],ly[N];//x,y中的点标号int n;LL slack[N];bool visx[N],visy[N];LL ans=INF;int T,a[N][N],b[N][N];struct P{ int x,y; P(){x=y=0;} P(int _x,int _y){x=_x;y=_y;} P operator-(const P&rhs){return P(x-rhs.x,y-rhs.y);}}l,r;LL cross(P a,P b){return (LL)a.x*b.y-(LL)a.y*b.x;}bool DFS(int x){ visx[x]=1; for(int y=0;y<ny;y++){ if(visy[y])continue; int tmp=lx[x]+ly[y]-g[x][y]; if(tmp==0){ visy[y]=true; if(linker[y]==-1||DFS(linker[y])){ linker[y]=x; return 1; } }else if(slack[y]>tmp)slack[y]=tmp; }return 0;}P KM(){ P p; memset(linker,-1,sizeof(linker)); memset(ly,0,sizeof(ly)); for(int i=0;i<nx;i++){ lx[i]=-INF; for(int j=0;j<ny;j++)if(g[i][j]>lx[i])lx[i]=g[i][j]; } for(int x=0;x<nx;x++){ for(int i=0;i<ny;i++)slack[i]=INF; for(;;){ memset(visx,false,sizeof(visx)); memset(visy,false,sizeof(visy)); if(DFS(x))break; LL d=INF; for(int i=0;i<ny;i++)if(!visy[i]&&d>slack[i])d=slack[i]; for(int i=0;i<nx;i++)if(visx[i])lx[i]-=d; for(int i=0;i<ny;i++){ if(visy[i])ly[i]+=d; else slack[i]-=d; } } }LL res=0; for(int i=0;i<ny;i++)if(linker[i]!=-1){ p.x+=a[linker[i]][i]; p.y+=b[linker[i]][i]; }res=(LL)p.x*p.y; if(res<ans)ans=res; return p;}void work(P l,P r){ P t=l-r; for(int i=0;i<n;i++)for(int j=0;j<n;j++)g[i][j]=-cross(P(a[i][j],b[i][j]),t); P mid=KM(); if(cross(mid-l,r-mid)>0)work(l,mid),work(mid,r);}int main(){ scanf("%d",&T); while(T--){ scanf("%d",&n); ans=INF; for(int i=0;i<n;i++)for(int j=0;j<n;j++)scanf("%d",&a[i][j]); for(int i=0;i<n;i++)for(int j=0;j<n;j++)scanf("%d",&b[i][j]); nx=ny=n; for(int i=0;i<n;i++)for(int j=0;j<n;j++)g[i][j]=-a[i][j]; l=KM(); for(int i=0;i<n;i++)for(int j=0;j<n;j++)g[i][j]=-b[i][j]; r=KM(); work(l,r); printf("%lld\n",ans); }return 0;}
BZOJ 3571 [Hnoi2014]画框(最小乘积完美匹配)
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。