[SDOI2017]新生舞会
题目大意:
有n个男生和n个女生跳舞。第i个男生和第j个女生组合会产生a[i][j]的喜悦程度和b[i][j]的不协调值。
现在你要找到一种方案,使喜悦程度总和与不协调值总和的比值最大。求这个比值。
解题思路:
分数规划问题。
即令\(\frac{\sum a}{\sum b}\)最大。
令其为C,则\(\sum a=C\sum b\)。
\(\sum a-C\sum b=0\)。
二分C,则若\(\sum a-C\sum b\geq 0\),则答案可行。
判断答案的可行性,发现这是个二分图带权匹配问题,跑最大费用最大流即可,两点之间的费用即为\(a[i][j]-C\times b[i][j]\)。
若费用大于等于0则可行。
C++ Code:
#include<bits/stdc++.h> const int S=0,T=202; int n,cnt; int a[105][105],b[105][105],head[205],fl[205],pre[205],q[40004]; int from[99999],to[99999],nxt[99999],cap[99999];double cost[99999]; bool vis[205]; double dis[205]; inline int min(int a,int b){return a<b?a:b;} void spfa(int&flow,double&C){ for(dis[S]=0;;){ for(int i=S+1;i<=T;++i)dis[i]=-1e17; memset(vis,0,sizeof vis); memset(fl,0x3f,sizeof fl); memset(pre,0,sizeof pre); vis[S]=1; int l=0,r=1; for(q[1]=S;l!=r;){ int u=q[l=l%40000+1]; vis[u]=false; for(int i=head[u];~i;i=nxt[i]) if(cap[i]&&dis[to[i]]<dis[u]+cost[i]){ dis[to[i]]=dis[u]+cost[i]; fl[to[i]]=min(fl[u],cap[i]); pre[to[i]]=i; if(!vis[to[i]]) vis[q[r=r%40000+1]=to[i]]=1; } } if(dis[T]<-1e16)return; flow+=fl[T]; C+=dis[T]*fl[T]; for(int i=T;i;i=from[pre[i]]){ --cap[pre[i]]; ++cap[pre[i]^1]; } } } bool check(double k){ cnt=1; for(int i=1;i<=n;++i){ cap[++cnt]=1; cap[++cnt]=0; cap[++cnt]=1; cap[++cnt]=0; for(int j=1;j<=n;++j){ cap[++cnt]=1; cost[cnt]=a[i][j]-k*b[i][j]; cap[++cnt]=0; cost[cnt]=-cost[cnt-1]; } } int flow=0;double C=0; spfa(flow,C); return C+1e-8>=0; } int main(){ memset(head,-1,sizeof head); scanf("%d",&n); cnt=1; for(int i=1;i<=n;++i){ from[++cnt]=S;to[cnt]=i;nxt[cnt]=head[S];head[S]=cnt;cost[cnt]=0; from[++cnt]=i;to[cnt]=S;nxt[cnt]=head[i];head[i]=cnt;cost[cnt]=0; from[++cnt]=i+n;to[cnt]=T;nxt[cnt]=head[i+n];head[i+n]=cnt;cost[cnt]=0; from[++cnt]=T;to[cnt]=i+n;nxt[cnt]=head[T];head[T]=cnt;cost[cnt]=0; for(int j=1;j<=n;++j){ from[++cnt]=i;to[cnt]=j+n;nxt[cnt]=head[i];head[i]=cnt; from[++cnt]=j+n;to[cnt]=i;nxt[cnt]=head[j+n];head[j+n]=cnt; } } 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=1e6,ans=0; while(l+1e-8<r){ double mid=(l+r)/2; if(check(mid))l=mid+1e-8,ans=mid; else r=mid-1e-8; } printf("%.6f\n",ans); return 0; }