P3705 [SDOI2017] 新生舞会

P3705 [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\) 值最大。

输出格式

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

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

Solution:

拿到题目的第一眼:完了我不会,再打开题目标签一看,
二分?费用流?我直接悟了

二分肯定是针对C来进行二分了,然后我们先推一下式子:
C=\(\frac{\sum{a}}{\sum{b}}\)
\(C \times \sum{b} = \sum{a}\)
\(\sum{a}-C \times \sum{b}=0\)
然后我们再来看看x是怎么选的:
“有 \(n\) 个男生和 \(n\) 个女生参加舞会,一个男生和一个女生一起跳舞,互为舞伴”
也就是说,对于这整个网格图来说,每一行配对一列,然后检查当前的C是否可行。

那么如何求出\(\sum{a}\)\(\sum{b}\)才能使得针对C的check变得充分呢?
还记得之前我们看到过这题的第二个tag是费用流,所有我们不难想到利用\(a-C*b\)来进行建图:

S向每个男生连一条流量为1,费用为0的边
每个男生向每个女生连一条流量为1费用为\(a-C*b\)的边
每个女生向T连一条流量为1,费用为0的边

然后对于这个网络跑最大费用最大流,显然最大流一定能跑满n,这就保证了方案的可行性,然后在check一下费用是否大于等于0来作为check函数的返回值

然后就是注意一下由于本题是对一个小数进行二分,所以精度不能给得太高,我设的eps是1e-8,这样右边界转化为整数就是1e14左右,还是可以接受的,再有就是要注意dinic时是否忘记修改流量或者spfa时忘记修改队列状态而导致的TLE,RE等一系列问题半个小时的血泪教训

最短路只写dijkstra导致spfa丢三落四导致的

#include<bits/stdc++.h>
const int N=505;
const int ID=105;
const int inf=1e9;
const double eps=1e-8;
using namespace std;
int n,m,e_cnt=1,S,T;
int head[N],a[N][N],b[N][N];
struct Edge{
    int to,nxt,fl;
    double w;
}e[N*N];
void add(int x,int y,int fl,double w)
{
    e[++e_cnt]=(Edge){y,head[x],fl,w};head[x]=e_cnt;
    e[++e_cnt]=(Edge){x,head[y],0,-w};head[y]=e_cnt;
    //<<e_cnt<<"="<<x<<" "<<y<<" "<<fl<<" "<<w<<"\n";
}
double dis[N];
int vis[N],flow[N],dl[N];
int pre[N];
void init()
{
    for(int i=S;i<=T;i++)
    {
        dis[i]=-inf;
        flow[i]=vis[i]=pre[i]=0;
    }
}
queue<int> Q;
bool spfa(int s,int t)
{
    init();
    dis[s]=0;flow[s]=inf;
    Q.push(s);
    while(!Q.empty())
    {
        int u=Q.front();Q.pop();
        dl[u]=0;
        if(++vis[u]>n)continue;
        //<<u<<" "<<dis[u]<<"\n";
        for(int i=head[u];i;i=e[i].nxt)
        {
            int v=e[i].to,fl=e[i].fl;
            //<<v<<"="<<fl<<"  ="<<dis[v]<<"   "<<dis[u]+e[i].w<<"\n";
            if(fl&&dis[v]<dis[u]+e[i].w)
            {
                dis[v]=dis[u]+e[i].w;
                flow[v]=min(flow[u],fl);
                pre[v]=i;
                if(!dl[v])
                {
                    dl[v]=1;Q.push(v);
                }
            }
        }
        //<<"\n";
    }
    return flow[t];
}
bool dinic()
{
    double ans=0;
    while(spfa(S,T))
    {
        int now=T,fl=flow[T];
        ans+=1.0*fl*dis[T];
        while(now!=S)
        {
            int id=pre[now];
            e[id].fl-=fl;e[id^1].fl+=fl;
            //<<now<<" ";
            now=e[id^1].to;
        }
        //<<"="<<1.0*fl*dis[T]<<"\n";
    }
    //<<ans<<"\n";
    return ans>=0;
}
void build(double k)
{
    for(int i=1;i<=n;i++)
    {
        add(S,i,1,0);add(i+ID,T,1,0);
        for(int j=1;j<=n;j++)
        {
            add(i,j+ID,1,1.0*a[i][j]-1.0*k*b[i][j]);
        }
    }
}
void INIT()
{
    e_cnt=1;
    for(int i=S;i<=T;i++)
    {
        head[i]=0;
    }
}
bool check(double k)
{
    INIT();
    build(k);
    return dinic();
}
void work()
{
    cin>>n;
    S=0,T=ID<<1|1;
    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]);}}
    double l=0,r=1000000,ans=0;
    while(l+eps<r)
    {
        double mid=(l+r)/2;
        if(check(mid))
        {
            ans=mid;
            l=mid;
        }
        else
        {
            r=mid-eps;
        }
        //cout<<l<<r<<""
    }
    printf("%lf",ans);
}
int main()
{
    //freopen("dance.in","r",stdin);
    //freopen("dance.out","w",stdout);
    work();
    return 0;
}
posted @ 2024-12-06 11:58  liuboom  阅读(62)  评论(0)    收藏  举报