测试地址:新生舞会
做法:本题需要用到01分数规划+费用流。
首先看到题目中那个式子,就差不多能想到01分数规划了。按照套路处理:二分比值
C
C
,转化为判定性问题,即存不存在大于的比值,把分母乘到另一边,再减回来,得到
∑Ai−C⋅Bi>0
∑
A
i
−
C
⋅
B
i
>
0
这个式子,在这道题中,就是按边权为
Aij−C⋅Bij
A
i
j
−
C
⋅
B
i
j
建二分图,然后问有没有一个匹配的权值和大于
0
0
<script type="math/tex" id="MathJax-Element-5">0</script>,那么我们当然是求最佳匹配,这就是费用流的经典应用之一了。
(但是实际测试中,分点测试的情况下貌似会被卡,更好的KM算法有待学习)
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const double eps=1e-8;
const double inf=1000000000.0;
int n,s,t,first[210],tot;
int hd,tl,q[8000010],last[210],laste[210];
double a[110][110],b[110][110],dis[210];
bool vis[210]={0};
struct edge
{
int v,next,f;
double c;
}e[50010];
void insert(int a,int b)
{
e[++tot].v=b,e[tot].next=first[a],first[a]=tot;
e[++tot].v=a,e[tot].next=first[b],first[b]=tot;
}
void maxflow()
{
for(int i=1;i<=2*n+2;i++)
dis[i]=-inf;
dis[s]=0.0;
vis[s]=1;
q[1]=s;
hd=tl=1;
while(hd<=tl)
{
int v=q[hd++];
for(int i=first[v];i;i=e[i].next)
{
if (e[i].f&&dis[e[i].v]<dis[v]+e[i].c)
{
dis[e[i].v]=dis[v]+e[i].c;
last[e[i].v]=v;laste[e[i].v]=i;
if (!vis[e[i].v]) vis[e[i].v]=1,q[++tl]=e[i].v;
}
}
vis[v]=0;
}
}
bool check(double c)
{
double ans=0.0;
int step=0;
tot=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
e[++tot].f=1,e[tot].c=a[i][j]-c*b[i][j];
e[++tot].f=0,e[tot].c=c*b[i][j]-a[i][j];
}
for(int i=1;i<=n;i++)
{
e[++tot].f=1,e[tot].c=0.0;
e[++tot].f=0,e[tot].c=0.0;
e[++tot].f=1,e[tot].c=0.0;
e[++tot].f=0,e[tot].c=0.0;
}
while(step<n)
{
maxflow();
ans+=dis[t];
step++;
int x=t;
while(x!=s)
{
e[laste[x]].f--;
e[laste[x]^1].f++;
x=last[x];
}
}
return ans>eps;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%lf",&a[i][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%lf",&b[i][j]);
tot=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
insert(i,n+j);
s=2*n+1,t=2*n+2;
for(int i=1;i<=n;i++)
{
insert(s,i);
insert(n+i,t);
}
double l=0.0,r=10000.0;
while(r-l>=eps)
{
double mid=(l+r)/2.0;
if (check(mid)) l=mid;
else r=mid;
}
printf("%.6lf",l);
return 0;
}