P3705 [SDOI2017]新生舞会 分数规划+费用流
题意:
分析:
模拟赛出了这道题的弱化版,把矩阵从 (n^2) 变成了 (2 imes n) 的,被巨佬们用模拟退火退过去了。。。
现在我们考虑正解,题意等价于,从矩阵中选出 (n) 个点,满足任意两个点不属于同一行或列,求 (frac{sum a}{sum b}) 的最大值
我们发现这个式子长得很分数规划,所以我们化简得到 (sum a-ans*sum b=0) ,也就是说矩形内部每一个点的权值变成了 (a-ans imes b) 然后每行每列选一个点,使所有点的权值之和最大,既要满足个数要求和限制条件,同时还要求出最大代价,我们发现这不就是费用流吗
我们按照分数规划二分得到的值,将行 (i) 和列 (j) 之间连一条流量为 (1) 费用为 (ans*b[i][j]-a[i][j]) 的边,然后原点和每一行连一条流量为 (1) 费用为 (0) 的边,每一列和汇点连一条流量为 (1) 费用为 (0) 的边,这样直接跑网络流,我们就能保证最大流的情况下得到 (sum b*ans-sum a) 的最小值 (等价于 (sum a-ans imes sum b) 的最大值)
代码:
#include<bits/stdc++.h>
using namespace std;
namespace zzc
{
int read()
{
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
const double eps = 1e-8;
const int maxn = 205;
const int maxm = 1e5+5;
int n,cnt=1,st,ed;
int a[maxn][maxn],b[maxn][maxn];
int head[maxn],lst[maxn],pre[maxn],flow[maxn];
bool vis[maxn];
double dis[maxn];
queue<int> q;
struct edge
{
int to,nxt,w;
double cst;
}e[maxm];
void add(int u,int v,double w,int x)
{
e[++cnt].to=v;
e[cnt].cst=w;
e[cnt].w=x;
e[cnt].nxt=head[u];
head[u]=cnt;
}
bool spfa()
{
for(int i=st;i<=ed;i++) pre[i]=-1,vis[i]=false,dis[i]=1000000.0,flow[i]=0x3f3f3f3f;
dis[st]=0;
q.push(st);
while(!q.empty())
{
int u=q.front();q.pop();
vis[u]=false;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(e[i].w&&dis[v]>dis[u]+e[i].cst)
{
dis[v]=dis[u]+e[i].cst;
pre[v]=u;
lst[v]=i;
flow[v]=min(flow[u],e[i].w);
if(!vis[v])
{
vis[v]=true;
q.push(v);
}
}
}
}
return pre[ed]!=-1;
}
double MCMF()
{
double res=0;
while(spfa())
{
int now=ed;
res+=dis[now]*flow[now];
while(now!=st)
{
e[lst[now]].w-=flow[ed];
e[lst[now]^1].w+=flow[ed];
now=pre[now];
}
}
return res;
}
bool check(double x)
{
cnt=1;memset(head,0,sizeof(head));
for(int i=1;i<=n;i++)
{
add(st,i,0,1);add(i,st,0,0);
add(i+n,ed,0,1);add(ed,n+i,0,0);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
add(i,j+n,x*b[i][j]-a[i][j],1);add(j+n,i,-x*b[i][j]+a[i][j],0);
}
}
return MCMF()<0;
}
void solve()
{
double l=0,mid,r=1000000.0,ans;
while(r-l>eps)
{
mid=(l+r)/2.0;
if(check(mid))
{
ans=mid;
l=mid+eps;
}
else r=mid-eps;
}
printf("%.6f
",ans);
}
void init()
{
n=read();st=0;ed=n*2+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();
}
void work()
{
init();
solve();
}
}
int main()
{
zzc::work();
return 0;
}