[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); }
看都看了,顺手点个推荐呗 :)