您的位置:首页 > 其它

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

2016-05-16 19:17 288 查看
显然可以把一种匹配看成点(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

,b

,d
,pre
,h[M+5]; bool ok

,bo
;
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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: