见识到了大名鼎鼎的 分数规划…
稍微变化一下
也就是
很神奇吧?不复杂,却能巧妙地解决问题呢
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=2e5+10;
const double eps=1e-7;
const int inf=1e9;
int n,m,s,t;
double mincost;
int head[maxn<<1],cnt=1,incf[maxn],pre[maxn],vis[maxn];
int a[109][109],b[109][109];
struct edge{
int to,nxt,flow;double w;//分别代表
}d[maxn<<1];
void add(int u,int v,int flow,double w)//最大流量,单位费用
{
d[++cnt]=(edge){v,head[u],flow,w},head[u]=cnt;
d[++cnt]=(edge){u,head[v],0,-w},head[v]=cnt;
}
double dis[maxn];
bool spfa()
{
queue<int>q;
for(int i=0;i<=t;i++) dis[i]=-inf;
memset(vis,0,sizeof(vis));
q.push(s);
dis[s]=0,vis[s]=1;
incf[s] = inf;//初始流量无限大
while( !q.empty() )
{
int u=q.front(); q.pop();
vis[u]=0;//出队
for(int i=head[u];i;i=d[i].nxt)
{
if( !d[i].flow ) continue;//无流量了
int v=d[i].to;
if( dis[v]<dis[u]+d[i].w )
{
dis[v]=dis[u]+d[i].w;
incf[v] = min(incf[u],d[i].flow);//更新当前流量
pre[v]=i;//记录从哪条边过来的
if( !vis[v] ) vis[v]=1,q.push(v);
}
}
}
if( dis[t]==-inf ) return 0;
return 1;
}
void dinic()
{
while( spfa() )
{
int x=t;//倒回去找路径
mincost+=dis[t]*incf[t];
int i;
while(x != s)
{
i=pre[x];
d[i].flow-=incf[t];//减去流量
d[i^1].flow+=incf[t];//加上流量
x = d[i^1].to;//因为是倒回去,所以利用反向边倒回去
}
}
}
bool isok( double mid )
{
cnt=1;
memset(head,0,sizeof(head));
s=0,t=n+n+1;
for(int i=1;i<=n;i++) add(s,i,1,0);
for(int i=1;i<=n;i++) add(i+n,t,1,0);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
add(i,j+n,1,a[i][j]*1.0-mid*b[i][j] );
mincost=0;
dinic();
if( mincost>=0 ) return true;
return false;
}
signed main()
{
cin >> n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin >> a[i][j];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin >> b[i][j];
double l=0,r=1e6,mid,ans;
while( (r-l)>=eps )
{
mid=(l+r)/2.0;
if( isok(mid) ) l=mid,ans=mid;
else r=mid;
}
printf("%.6f",ans);
}