BZOJ4819 [Sdoi2017]新生舞会(洛谷P3705)

费用流 分数规划

BZOJ题目传送门
洛谷题目传送门

很显然这是个完美匹配问题。可以KM做,也可以费用流。KM要比费用流快很多,这里采用费用流。

分数规划套路一波。二分答案 mid m i d ,男生到女生连费用为 a[i][j]midb[i][j] a [ i ] [ j ] − m i d ∗ b [ i ] [ j ] ,容量为1的边,源点到每个男生和每个女生到汇点连费用为0,容量为1的边。跑最大费用最大流,如果费用 0 ≥ 0 则可行。

费用流有点卡常。可以写我不会的 zkw或者不要用结构体。

代码:

#include<cctype>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 205
#define M N*N
#define F inline
#define eps 1e-8
using namespace std;
typedef double DB;
int nxt[M],to[M],v[M],ef[M],fx[N],e[N];
int n,k,s,t,a[N][N],b[N][N],h[N],q[N*N],rem[N];
DB d[N],ed[M]; bool f[N];
F char readc(){
    static char buf[100000],*l=buf,*r=buf;
    if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
    if (l==r) return EOF; return *l++;
}
F int _read(){
    int x=0; char ch=readc();
    while (!isdigit(ch)) ch=readc();
    while (isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=readc();
    return x;
}
F void addedge(int x,int y,DB z){
    nxt[++k]=h[x],to[k]=y,v[k]=1,ef[k]=0,ed[k]=z,h[x]=k;
    nxt[++k]=h[y],to[k]=x,v[k]=ef[k]=0,ed[k]=-z,h[y]=k;
}
F bool spfa(){
    for (int i=s;i<=t;i++)
        f[i]=false,d[i]=-1e10;
    int l=0,r=1; rem[s]=1e9,d[q[1]=s]=0;
    while (l<=r){
        int x=q[++l]; f[x]=false;
        for (int i=h[x],p;~i;i=nxt[i])
            if (v[i]>ef[i]&&d[x]+ed[i]>d[p=to[i]]){
                rem[p]=min(rem[x],1);
                d[p]=d[x]+ed[i],fx[p]=x,e[p]=i;
                if (!f[p]) q[++r]=p,f[p]=true;
            }
    }
    return d[t]>-1e10?rem[t]:0;
}
F void mdfy(int sum){
    int x=t;
    while (x!=s)
        ef[e[x]]+=sum,ef[e[x]^1]-=sum,x=fx[x];
}
F bool pd(DB mid){
    DB cst=0; k=-1;
    for (int i=s;i<=t;i++) h[i]=-1;
    for (int i=1;i<=n;i++)
        addedge(s,i,0),addedge(i+n,t,0);
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
            addedge(i,j+n,1.0*a[i][j]-mid*b[i][j]);
    for (int sum=spfa();sum;sum=spfa())
        cst+=d[t]*sum,mdfy(sum);
    return cst>-eps;
}
int main(){
    n=_read(),t=n<<1|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();
    DB l=0,r=1e7,mid;
    while (r-l>eps)
        if (pd(mid=(l+r)/2)) l=mid+eps;
        else r=mid-eps;
    return printf("%.6f\n",l),0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值