P3705 [SDOI2017]新生舞会(01分数规划+费用流)

P3705 [SDOI2017]新生舞会

见识到了大名鼎鼎的 01 01 分数规划…

, . . . . 显然这是个二分图,但是这个价值计算放置太头疼了....

a i b i = m a x x 令\frac{\sum a_i}{\sum b_i}=maxx

稍微变化一下 m a x x b i = a i maxx*\sum b_i=\sum{a_i}

也就是 ( a i m a x x b i ) = 0 \sum (a_i-maxx*b_i)=0

m a x x , a i m a x x b i 那我们二分maxx,把边权看成a_i-maxx*b_i

, m a x x ! ! 求一个最大费用最大流,随着maxx的增大费用肯定会变小!!

m a x x , 0 , m a x x . 所以二分maxx,若此时费用仍然大于0,说明maxx可以继续增大.

很神奇吧?不复杂,却能巧妙地解决问题呢

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=2e5+10;
const double eps=1e-7;
const int inf=1e9;
int n,m,s,t;
double mincost;
int head[maxn<<1],cnt=1,incf[maxn],pre[maxn],vis[maxn];
int a[109][109],b[109][109];
struct edge{
	int to,nxt,flow;double w;//分别代表 
}d[maxn<<1];
void add(int u,int v,int flow,double w)//最大流量,单位费用
{
	d[++cnt]=(edge){v,head[u],flow,w},head[u]=cnt;
	d[++cnt]=(edge){u,head[v],0,-w},head[v]=cnt;
} 
double dis[maxn];
bool spfa()
{
	queue<int>q;
	for(int i=0;i<=t;i++)	dis[i]=-inf;
	memset(vis,0,sizeof(vis));
	q.push(s);
	dis[s]=0,vis[s]=1;
	incf[s] = inf;//初始流量无限大
	while( !q.empty() )
	{
		int u=q.front(); q.pop();
		vis[u]=0;//出队
		for(int i=head[u];i;i=d[i].nxt)
		{
			if( !d[i].flow )	continue;//无流量了	
			int v=d[i].to;
			if( dis[v]<dis[u]+d[i].w )
			{
				dis[v]=dis[u]+d[i].w;
				incf[v] = min(incf[u],d[i].flow);//更新当前流量
				pre[v]=i;//记录从哪条边过来的
				if( !vis[v] )	vis[v]=1,q.push(v); 
			}
		}	
	} 
	if( dis[t]==-inf )	return 0;
	return 1;
}
void dinic()
{
	while( spfa() )
	{
		int x=t;//倒回去找路径
		mincost+=dis[t]*incf[t];
		int i;
		while(x != s)
		{
			i=pre[x];
			d[i].flow-=incf[t];//减去流量
			d[i^1].flow+=incf[t];//加上流量
			x = d[i^1].to;//因为是倒回去,所以利用反向边倒回去 
		 } 
	}
}
bool isok( double mid )
{
	cnt=1;
	memset(head,0,sizeof(head));
	s=0,t=n+n+1;
	for(int i=1;i<=n;i++)	add(s,i,1,0);
	for(int i=1;i<=n;i++)	add(i+n,t,1,0);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
		add(i,j+n,1,a[i][j]*1.0-mid*b[i][j] );
	mincost=0;
	dinic();
	if( mincost>=0 )	return true;
	return false;
}
signed main()
{
	cin >> n;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
		cin >> a[i][j];
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
		cin >> b[i][j];
	double l=0,r=1e6,mid,ans;
	while( (r-l)>=eps )
	{
		mid=(l+r)/2.0;
		if( isok(mid) )	l=mid,ans=mid;
		else	r=mid;
	}
	printf("%.6f",ans);
}

猜你喜欢

转载自blog.csdn.net/jziwjxjd/article/details/108454760