【BZOJ4819】[Sdoi2017]新生舞会
Description
学校组织了一次新生舞会,Cathy作为经验丰富的老学姐,负责为同学们安排舞伴。有n个男生和n个女生参加舞会
买一个男生和一个女生一起跳舞,互为舞伴。Cathy收集了这些同学之间的关系,比如两个人之前认识没计算得出
a[i][j] ,表示第i个男生和第j个女生一起跳舞时他们的喜悦程度。Cathy还需要考虑两个人一起跳舞是否方便,
比如身高体重差别会不会太大,计算得出 b[i][j],表示第i个男生和第j个女生一起跳舞时的不协调程度。当然,
还需要考虑很多其他问题。Cathy想先用一个程序通过a[i][j]和b[i][j]求出一种方案,再手动对方案进行微调。C
athy找到你,希望你帮她写那个程序。一个方案中有n对舞伴,假设没对舞伴的喜悦程度分别是a'1,a'2,...,a'n,
假设每对舞伴的不协调程度分别是b'1,b'2,...,b'n。令
C=(a'1+a'2+...+a'n)/(b'1+b'2+...+b'n),Cathy希望C值最大。
Input
第一行一个整数n。
接下来n行,每行n个整数,第i行第j个数表示a[i][j]。
接下来n行,每行n个整数,第i行第j个数表示b[i][j]。
1<=n<=100,1<=a[i][j],b[i][j]<=10^4
Output
一行一个数,表示C的最大值。四舍五入保留6位小数,选手输出的小数需要与标准输出相等
Sample Input
3
19 17 16
25 24 23
35 36 31
9 5 6
3 4 2
7 8 9
19 17 16
25 24 23
35 36 31
9 5 6
3 4 2
7 8 9
Sample Output
5.357143
题解:正常的01分数规划步骤是,先二分答案r,然后将所有点的权值变成a[i]-r*b[i],取最大的k个数,如果最大的k个a[i]-r*b[i]的和>0,说明答案还可以更优,调整下界;否则调整上界
对于本题来说,我们不能直接排序取前n大,因此需要跑流量为n的最大费用流,如果总费用>0,则说明答案可以更优
(最大费用流不用说了吧?将边的费用取相反数,然后跑最小费用流)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
int n,cnt,ans;
double sum;
int inq[210],head[210],next[100000],to[100000],flow[100000],re[210],rv[210];
double dis[210],cost[100000];
int A[110][110],B[110][110];
queue<int> q;
void add(int a,int b,double c)
{
to[cnt]=b,cost[cnt]=-c,flow[cnt]=1,next[cnt]=head[a],head[a]=cnt++;
to[cnt]=a,cost[cnt]=c,flow[cnt]=0,next[cnt]=head[b],head[b]=cnt++;
}
int bfs()
{
int i,u;
for(i=1;i<=2*n+1;i++) dis[i]=9999999;
dis[0]=0,q.push(0);
while(!q.empty())
{
u=q.front(),q.pop(),inq[u]=0;
for(i=head[u];i!=-1;i=next[i])
{
if(flow[i]&&dis[to[i]]>dis[u]+cost[i])
{
dis[to[i]]=dis[u]+cost[i];
re[to[i]]=i,rv[to[i]]=u;
if(!inq[to[i]]) inq[to[i]]=1,q.push(to[i]);
}
}
}
return dis[2*n+1]<9999999-1e-8;
}
bool solve(double sta)
{
memset(head,-1,sizeof(head));
int i,j;
cnt=0,sum=0;
for(i=1;i<=n;i++)
{
add(0,i,0),add(i+n,2*n+1,0);
for(j=1;j<=n;j++) add(i,j+n,(double)A[i][j]-sta*B[i][j]);
}
while(bfs())
{
sum+=dis[2*n+1];
for(i=2*n+1;i;i=rv[i]) flow[re[i]]--,flow[re[i]^1]++;
}
return sum<-1e-8;
}
int main()
{
scanf("%d",&n);
int i,j,maxx;
double l=0,r=0,mid;
for(i=1;i<=n;i++)
{
maxx=0;
for(j=1;j<=n;j++)
{
scanf("%d",&A[i][j]);
maxx=max(maxx,A[i][j]);
}
r+=maxx;
}
for(i=1;i<=n;i++) for(j=1;j<=n;j++) scanf("%d",&B[i][j]);
while(r>=l+1e-8)
{
mid=(l+r)/2;
if(solve(mid)) l=mid;
else r=mid;
}
printf("%.6f",l);
return 0;
}