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

我果然是啥都不会了呢

首先,我们来观察题目这个柿子,由于是一个分数的形式,所以会容易想到用分数规划来尝试一下能不能解决这个问题。

那么假设我们二分的答案 m i d &lt; C mid&lt;C mid<C
也就是说 m i d &lt; ∑ a ∑ b mid &lt; \frac{\sum a}{\sum b} mid<ba

那么考虑移项 m i d × ∑ b &lt; ∑ a mid \times \sum b &lt; \sum a mid×b<a

∑ ( a − m i d × b ) &gt; 0 \sum (a - mid\times b)&gt; 0 (amid×b>0

不难发现这是一个分数规划的经典的柿子。

那么,也就是说,如果我们调整选每一对男女的贡献,让他的总和大于0即可。

qwq
由于是二分图带权匹配,所以采用费用流

s s s向每个男生连费用是0,流量是1的边,女生向 t t t同理

然后每一个男生向对应的女生连费用是 a i j − m i d × b i j a_{ij}-mid\times b_{ij} aijmid×bij,流量是1。

然后跑一个最大费用费用流
看一下是不是大于0即可

如果大于0,就调大 m i d mid mid,否则调小

// luogu-judger-enable-o2
// luogu-judger-enable-o2
// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define mk make_pair
#define ll long long

using namespace std;

inline int read()
{
  int x=0,f=1;char ch=getchar();
  while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
  while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}

const int maxn = 410;
const int maxm = 4e5+1e2;
const int inf = 1e9;
const double eps = 1e-9;

int point[maxn],nxt[maxm],to[maxm];
int flow[maxm];
double cost[maxm];
int cnt=1,n,m;
int vis[maxn];
double dis[maxn];
int from[maxn],pre[maxm];
int s,t;
double anscost;
double a[maxn][maxn];
double b[maxn][maxn];

void addedge(int x,int y,double w,int f)
{
    nxt[++cnt]=point[x];
    to[cnt]=y;
    pre[cnt]=x;
    cost[cnt]=w;
    flow[cnt]=f;
    point[x]=cnt;
}

void insert(int x,int y,double w,int f)
{
    addedge(x,y,w,f);
    addedge(y,x,-w,0); 
}

void init()
{
    cnt=1;
    memset(point,0,sizeof(point));
    memset(from,0,sizeof(from));
    anscost=0;
}

queue<int> q;

bool spfa(int s)
{
    for (int i=1;i<=t;i++) dis[i]=-inf;
    memset(vis,0,sizeof(vis));
    dis[s]=0;
    vis[s]=1;
    q.push(s);
    while (!q.empty())
    {
    	int x = q.front();
    	q.pop();
    	vis[x]=0;
    	for (int i=point[x];i;i=nxt[i])
    	{
    		int p = to[i];
    		if (flow[i]>0 && dis[p]<dis[x]+cost[i])
    		{
    			from[p]=i;
    			dis[p]=dis[x]+cost[i];
    			if(!vis[p])
    			{
    				vis[p]=1;
    				q.push(p);
                }
            }
        }
    }
    if (dis[t]<=-inf) return 0;
    return 1;
}

void mcf()
{
    int x = inf;
    for (int i=from[t];i;i=from[pre[i]])
      x=min(x,flow[i]);
    for (int i=from[t];i;i=from[pre[i]])
    {
        anscost+=1.0*x*cost[i];
        flow[i]-=x;
        flow[i^1]+=x;
    }
}

void solve()
{
    while (spfa(s)) mcf();
}

bool check(double mid)
{
    init();
    s=maxn-10;
    t=s+1;
    for (int i=1;i<=n;i++) insert(s,i,0,1);
    for (int i=1;i<=n;i++)
      for (int j=1;j<=n;j++)
        insert(i,j+n,a[i][j]-b[i][j]*mid,1);
    for (int i=1;i<=n;i++) insert(i+n,t,0,1);
    solve();
//	cout<<anscost<<endl;
    if (anscost>-eps) return 1;
    else return 0;
}
int main()
{
  n=read();
  for (int i=1;i<=n;i++) 
    for (int j=1;j<=n;j++) 
      scanf("%lf",&a[i][j]);
  for (int i=1;i<=n;i++)
    for (int j=1;j<=n;j++)
      scanf("%lf",&b[i][j]);
  double l=0,r=1e6;
  double ans=0;
  while(r-l>=eps)
  {
  	 double mid = (l+r)/2;
  	 if (check(mid)) l=mid,ans=mid;
  	 else r=mid;
  }
  printf("%.6lf\n",ans); 
  return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值