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;
}

猜你喜欢

转载自www.cnblogs.com/nianheng/p/9814480.html