显然可以把一种匹配看成点(x,y),x为第一种边权的和,y为第二种边权的和。那么这个匹配的值为x*y,题中要寻找最小的x*y。
令k=x*y,那么对于匹配(x,y),在直线y=k/x上方的匹配都是没有它更优的;求最优乘积匹配的方法同最小乘积生成树。首先找到x最小的匹配(x1,y1)和y最小的匹配(x2,y2)作为初始的边界。令(x1,y1)为左边界,(x2,y2)为右边界的解为solve(x1,y1,x2,y2)。我们找到离直线(x1,y1)->(x2,y2)最远的且在直线下方的(x3,y3),用x3*y3更新答案,然后递归(x1,y1,x3,y3)和(x3,y3,x2,y2)即可。
AC代码如下:
#include<iostream> #include<cstdio> #include<cstring> #define N 155 #define M 40005 #define inf 1000000000 using namespace std; int n,gol,ans,a[N][N],b[N][N],d[N],pre[N],h[M+5]; bool ok[N][N],bo[N]; struct point{ int x,y; }; bool spfa(int u,int v){ memset(bo,1,sizeof(bo)); memset(d,0x3f,sizeof(d)); d[0]=0; int head=0,tail=1; h[1]=0; while (head!=tail){ head=head%M+1; int x=h[head],y,z; bo[x]=1; for (y=1; y<=gol; y++) if (ok[x][y] && (z=d[x]+a[x][y]*u+b[x][y]*v)<d[y]){ d[y]=z; pre[y]=x; if (bo[y]){ bo[y]=0; tail=tail%M+1; h[tail]=y; } } } return d[gol]<inf; } point calc(int u,int v){ int i,j,x=0,y=0; for (i=1; i<=n; i++) for (j=1; j<=n; j++){ ok[i][j+n]=1; ok[j+n][i]=0; } for (i=1; i<=n; i++){ ok[0][i]=ok[i+n][gol]=1; ok[i][0]=ok[gol][i+n]=0; } while (spfa(u,v)) for (i=gol; i; i=pre[i]){ ok[pre[i]][i]=0; ok[i][pre[i]]=1; x+=a[pre[i]][i]; y+=b[pre[i]][i]; } ans=min(ans,x*y); return (point){x,y}; } void solve(point u,point v){ point p=calc(u.y-v.y,v.x-u.x); if ((v.x-p.x)*(u.y-p.y)-(u.x-p.x)*(v.y-p.y)){ solve(u,p); solve(p,v); } } int main(){ int cas; scanf("%d",&cas); while (cas--){ scanf("%d",&n); ans=inf; int i,j; for (i=1; i<=n; i++) for (j=1; j<=n; j++){ scanf("%d",&a[i][j+n]); a[j+n][i]=-a[i][j+n]; } for (i=1; i<=n; i++) for (j=1; j<=n; j++){ scanf("%d",&b[i][j+n]); b[j+n][i]=-b[i][j+n]; } gol=n<<1|1; point u=calc(1,0),v=calc(0,1); solve(u,v); printf("%d\n",ans); } return 0; }
by lych
2016.5.16