【BZOJ4819】[Sdoi2017]新生舞会 01分数规划+费用流

【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

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;
}
posted @ 2017-04-25 12:27  CQzhangyu  阅读(281)  评论(0编辑  收藏  举报