[SDOI2017] 新生舞会——二分 最大费用最大流

[SDOI2017] 新生舞会

题目描述

学校组织了一次新生舞会,Cathy 作为经验丰富的老学姐,负责为同学们安排舞伴。

\(n\) 个男生和 \(n\) 个女生参加舞会,一个男生和一个女生一起跳舞,互为舞伴。

Cathy 收集了这些同学之间的关系,比如两个人之前认识没,计算得出 \(a_{i,j}\)

Cathy 还需要考虑两个人一起跳舞是否方便,比如身高体重差别会不会太大,计算得出 \(b_{i,j}\),表示第 \(i\) 个男生和第 \(j\) 个女生一起跳舞时的不协调程度。

当然,还需要考虑很多其他问题。

Cathy 想先用一个程序通过 \(a_{i,j}\)\(b_{i,j}\) 求出一种方案,再手动对方案进行微调。

Cathy 找到你,希望你帮她写那个程序。

一个方案中有 n 对舞伴,假设每对舞伴的喜悦程度分别是 \(a'_1,a'_2,...,a'_n\),假设每对舞伴的不协调程度分别是 \(b'_1,b'_2,...,b'_n\)。令

\(C=\frac {a'_1+a'_2+...+a'_n}{b'_1+b'_2+...+b'_n}\)

Cathy 希望 \(C\) 值最大。

输入格式

第一行一个整数 \(n\)

接下来 \(n\) 行,每行 \(n\) 个整数,第 \(i\) 行第 \(j\) 个数表示 \(a_{i,j}\)

接下来 \(n\) 行,每行 \(n\) 个整数,第 \(i\) 行第 \(j\) 个数表示 \(b_{i,j}\)

输出格式

一行一个数,表示 \(C\) 的最大值。四舍五入保留 \(6\) 位小数,选手输出的小数需要与标准输出相等。

样例 #1

样例输入 #1

3
19 17 16
25 24 23
35 36 31
9 5 6
3 4 2
7 8 9

样例输出 #1

5.357143

提示

对于 10% 的数据,\(1\le n\le 5\)

对于 40% 的数据,\(1\le n\le 18\)

另有 20% 的数据,\(b_{i,j}\le 1\)

对于 100% 的数据,\(1\le n\le 100,1\le a_{i,j},b_{i,j}\le10^4\)

分析

二分C值,用最大费用最大流检查是否满足要求即可。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+100;
long long INF=0x7fffffff;
struct edge{int y,n,x,z;double sp;}e[N<<1],t[N<<1];
double eps=0.00000001;
int vis[N],dfn,q[N],pre[N];
int n,sta,edn;
int head[N],cnt=1;
int a[210][210],b[210][210];
double dis[N];
void ad(int x,int y,int z)
{
    e[++cnt].n=head[x];
    e[cnt].y=y;
    e[cnt].x=x;
    e[cnt].z=z;
    head[x]=cnt;
}
void init()
{
    INF*=INF;
    scanf("%d",&n);
    sta=n*2+1;
    edn=n*2+2;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
            scanf("%d",&a[i][j]);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
            scanf("%d",&b[i][j]);
    for(int i=1;i<=n;++i)
    {
        ad(sta,i,1);
        ad(i,sta,0);
        ad(i+n,edn,1);
        ad(edn,i+n,0);
        for(int j=1;j<=n;++j)
        {
            ad(i,j+n,1);
            ad(j+n,i,0);
        }
    }
}
void ISAP()
{
    dis[0]=-INF;
    for(int i=1;i<=edn;++i)
        dis[i]=-INF;
    dis[sta]=0;
    ++dfn;

    int he=1,ta=0;
    q[++ta]=sta;
    while(he<=ta)//break the limit
    {
        int u=q[he++];
        vis[u]=dfn-1;

        for(int i=head[u];i;i=e[i].n)
        {
            int v=e[i].y;
            if(t[i].z>0 && dis[v]<dis[u]+t[i].sp)
            {
                dis[v]=dis[u]+t[i].sp;
                pre[v]=i;
                if(vis[v]!=dfn)
                {
                    q[++ta]=v;
                    vis[v]=dfn;
                }
            }
        }
    }
}
double returnflow()
{
    int u=edn,mxflow=114514;
    double spe=0;
    while(u!=sta)
    {
        mxflow=min(mxflow,t[pre[u]].z);
        u=e[pre[u]].x;
    }
    u=edn;
    while(u!=sta)
    {
        spe+=t[pre[u]].sp*mxflow;
        t[pre[u]].z-=mxflow;
        t[pre[u]^1].z+=mxflow;
        u=e[pre[u]].x;
    }
    return spe;
}
bool check(double lim)
{
    for(int i=2;i<=cnt;i+=2)
    {
        t[i].z=e[i].z;
        t[i^1].z=e[i^1].z;
        int x=e[i].x,y=e[i].y;
        if(x>n && x<=n*2)x-=n;
        if(y>n && y<=n*2)y-=n;
        double num=1.0*a[x][y]-1.0*lim*b[x][y];
        t[i].sp=num;
        t[i^1].sp=-num;
    }
    double mxflow=0;
    while(1)
    {
        ISAP();
        if(dis[edn]==dis[0])break;
        mxflow+=returnflow();
    }
    return mxflow>=0;
}
void work()
{
    double L=0,R=1e8+1000,mid=0,ans=-1;
    while(R-L>=eps)
    {
        mid=(L+R)/2;
        if(check(mid)){ans=mid;L=mid;}
        else R=mid;
    }
    printf("%.6lf",ans);//too small
}
int main()
{
    init();
    work();
    return 0;
}









posted @ 2024-10-13 16:15  Glowingfire  阅读(2)  评论(0编辑  收藏  举报