bzoj 3571: [Hnoi2014]画框 最优乘积匹配

       显然可以把一种匹配看成点(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

你可能感兴趣的:(费用流,最优匹配,最优乘积匹配)