[bzoj4819] [Sdoi2017] 新生舞会
题面
学校组织了一次新生舞会,Cathy作为经验丰富的老学姐,负责为同学们安排舞伴。有n个男生和n个女生参加舞会买一个男生和一个女生一起跳舞,互为舞伴。Cathy收集了这些同学之间的关系,比如两个人之前认识没计算得出 a[i][j] ,表示第i个男生和第j个女生一起跳舞时他们的喜悦程度。Cathy还需要考虑两个人一起跳舞是否方便,比如身高体重差别会不会太大,计算得出 b[i][j],表示第i个男生和第j个女生一起跳舞时的不协调程度。当然,还需要考虑很多其他问题。Cathy想先用一个程序通过a[i][j]和b[i][j]求出一种方案,再手动对方案进行微调。Cathy找到你,希望你帮她写那个程序。一个方案中有n对舞伴,假设没对舞伴的喜悦程度分别是a'1,a'2,...,a'n,假设每对舞伴的不协调程度分别是b'1,b'2,...,b'n。令C=(a'1+a'2+...+a'n)/(b'1+b'2+...+b'n),Cathy希望C值最大。
题解
分数规划+网络流
二分答案mid,将每个点的值看作a-b*mid。
这题考虑对于每一个mid,求出最大方案
由于每个男生只能搭配一名不同的女生,所以问题可以转化为:1个n*n的矩阵中每个位置都有1个数,求选出n个彼此不在同一行或同一列的数的和的最大值是多少。
加边$s->i(1,0),i+n->t(1,0),i->j+n(1,v[i][j])$,跑最大费用最大流,结果其实就是$sum(a_i)-a*sum(b_i)$
代码
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#include <cmath>
using namespace std;
const int N=50000,st=N-9,ed=N-8;
#define inf 10000
#define rev(i) (((i-1)^1)+1)
int head[N],cnt,to[N],nxt[N],n,m,cap[N],lev[N];
double cost[N],dis[N],ans;
bool vis[N],vis2[N];
void connect(int a,int b,int d,double c)
{
to[++cnt]=b,cost[cnt]=c,cap[cnt]=d,nxt[cnt]=head[a],head[a]=cnt;
to[++cnt]=a,cost[cnt]=-c,cap[cnt]=0,nxt[cnt]=head[b],head[b]=cnt;
}
bool spfa()
{
queue<int> q;
memset(lev,0,sizeof(lev));
for(int i=1;i<N;i++) dis[to[i]]=-99999999;
q.push(st);
dis[st]=0;
while(!q.empty())
{
int now=q.front();
q.pop();
vis[now]=false;
//cout<<now<<endl;
for(int i=head[now];i;i=nxt[i])
{
if(!cap[i]) continue;
double d=dis[now]+cost[i];
if(d<=dis[to[i]]) continue;
dis[to[i]]=d;
if(!vis[to[i]]) q.push(to[i]);
vis[to[i]]=true;
}
}
if(dis[ed]<-999999) return false;
return true;
}
int dfs(int id,int flow)
{
if(id==ed||!flow) return flow;
int t=flow;
vis2[id]=true;
for(int i=head[id];i;i=nxt[i])
{
if((vis2[to[i]]&&to[i]!=ed)||dis[to[i]]!=dis[id]+cost[i]||!cap[i]) continue;
int f=dfs(to[i],min(cap[i],flow));
ans+=f*cost[i];
flow-=f;
cap[i]-=f;
cap[rev(i)]+=f;
}
//if(flow) lev[id]=0;
return t-flow;
}
int dinic(int flow)
{
int ans=0;
while(spfa())
{
ans+=dfs(st,flow);
memset(vis2,0,sizeof(vis2));
//cout<<ans<<endl;
}
return ans;
}
int val[110][110],weight[110][110];
bool check(double mid)
{
memset(head,0,sizeof(head)),cnt=0;
ans=0;
for(int i=1;i<=n;i++) connect(st,i,1,0);
for(int i=1;i<=n;i++) connect(i+n,ed,1,0);
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) connect(i,j+n,1,val[i][j]-mid*weight[i][j]);
dinic(inf);
return ans>0;
}
signed main()
{
int tot=0;
cin>>n;
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%d",&val[i][j]),tot+=val[i][j];
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%d",&weight[i][j]);
double l=0,r=tot;
while(r-l>0.0000005)
{
double mid=(l+r)/2.0;
if(check(mid)) l=mid;
else r=mid;
}
printf("%.6f",l);
}
看都看了,顺手点个推荐呗 :)

浙公网安备 33010602011771号