题目大意:
有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;
}