Luogu-3705 [SDOI2017]新生舞会

分数规划,最大费用最大流#

题意可以简化为给出一个矩阵,要求每行和每列必须且只能取一个格子,要求\(sigma\ a_{i,j}/sigma\ b_{i,j}\) 最大

考虑分数规划,可以将式子转化:

\(sigma\ a_{i,j}/sigma\ b_{i,j}=C\)

\(sigma\ a_{i,j}=sigma\ b_{i,j}*C\)

\(sigma\ a_{i,j}-sigma\ b_{i,j}*C=0\)

\(sigma(\ a_{i,j}-b_{i,j}*C)=0\)

C就是我们要求的最大值,我们可以\(mid\)实数二分它,对于每一个\(mid\),求出这种情况下\(sigma(\ a_{i,j}-b_{i,j}*mid)=0\)的最大值,如果最大值小于0,就说明\(mid>C\),反之亦然。

至于怎么求最大值,可以将横坐标建一个点集,纵坐标建一个点集,对于每个矩阵上的点\(a_{i,j}\)建一条从i到j的弧,流量为1,费用为\(a_{i,j}-sigma\ b_{i,j}*mid\),然后跑最大费用最大流就行了

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<map>
#define inf 0x7fffffff
using namespace std;
inline int read()
{
	int ans=0,fh=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')
			fh=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
		ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
	return ans*fh;
}
const int maxn=300;
const int maxm=10010;
const double eps=0.00000001;
int s,t,v[maxm*2],u[maxm*2],w[maxm*2],qq[maxn],ll[maxn],nex[maxm*2],head[maxn],num=1,n,a[110][110],b[110][110];
double f[maxm*2],bj[maxn],l,r,mid;
bool cz[maxn];
queue<int>q;
void add(int x,int y,double fee)
{
	u[++num]=x;
	v[num]=y;
	w[num]=1;
	f[num]=fee;
	nex[num]=head[x];
	head[x]=num;
	u[++num]=y;
	v[num]=x;
	w[num]=0;
	f[num]=-fee;
	nex[num]=head[y];
	head[y]=num;
}
bool spfa()
{
	memset(qq,0,sizeof(qq));
	for(int i=1;i<=n*2+2;i++)
		bj[i]=2100000000;
	memset(ll,0,sizeof(ll));
	q.push(s);
	bj[s]=0;
	ll[s]=inf;
	while(!q.empty())
	{
		int now=q.front();
		q.pop();
		cz[now]=0;
		for(int i=head[now];i;i=nex[i])
			if(w[i]&&bj[v[i]]>bj[now]+f[i])
			{
				bj[v[i]]=bj[now]+f[i];
				ll[v[i]]=min(w[i],ll[now]);
				qq[v[i]]=i;
				if(!cz[v[i]])
					q.push(v[i]),cz[v[i]]=1;
			}
	}
	return qq[t]>0;
}
double EK()
{
	double fee=0;
	while(spfa())
	{
		int liu=ll[t];
		for(int i=qq[t];i;i=qq[u[i]])
			w[i]-=liu,w[i^1]+=liu;
		fee+=liu*bj[t];
	}
	return fee*-1;
}//最大费用最大流
double work(double x)
{
	memset(head,0,sizeof(head));
	num=1;
	for(int i=1;i<=n;i++)
		add(s,i,0),add(i+n,t,0);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			add(i,j+n,(double)x*b[i][j]-a[i][j]);//建图
	return EK();
}
int main()
{
	n=read();
	s=n*2+1;t=s+1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			a[i][j]=read();
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			b[i][j]=read();
	r=1000000;
	while(r-l>eps)
	{
		mid=(l+r)*0.5;
		double dd=work(mid);
		if(dd>=0)
			l=mid;
		else
			r=mid;
	}//实数二分
	printf("%.6lf",l);
	fclose(stdin);
	return 0;
}

posted @ 2018-10-19 09:06  nianheng  阅读(92)  评论(0编辑  收藏  举报