P3705 [SDOI2017] 新生舞会

P3705 [SDOI2017] 新生舞会

题目描述

学校组织了一次新生舞会,Cathy 作为经验丰富的老学姐,负责为同学们安排舞伴。

n 个男生和 n 个女生参加舞会,一个男生和一个女生一起跳舞,互为舞伴。

Cathy 收集了这些同学之间的关系,比如两个人之前认识没,计算得出 ai,j

Cathy 还需要考虑两个人一起跳舞是否方便,比如身高体重差别会不会太大,计算得出 bi,j,表示第 i 个男生和第 j 个女生一起跳舞时的不协调程度。

当然,还需要考虑很多其他问题。

Cathy 想先用一个程序通过 ai,jbi,j 求出一种方案,再手动对方案进行微调。

Cathy 找到你,希望你帮她写那个程序。

一个方案中有 n 对舞伴,假设每对舞伴的喜悦程度分别是 a1,a2,...,an,假设每对舞伴的不协调程度分别是 b1,b2,...,bn。令

C=a1+a2+...+anb1+b2+...+bn

Cathy 希望 C 值最大。

输出格式

一行一个数,表示 C 的最大值。四舍五入保留 6 位小数,选手输出的小数需要与标准输出相等。

对于 100% 的数据,1n100,1ai,j,bi,j104\

Solution:

拿到题目的第一眼:完了我不会,再打开题目标签一看,
二分?费用流?我直接悟了

二分肯定是针对C来进行二分了,然后我们先推一下式子:
C=ab
C×b=a
aC×b=0
然后我们再来看看x是怎么选的:
“有 n 个男生和 n 个女生参加舞会,一个男生和一个女生一起跳舞,互为舞伴”
也就是说,对于这整个网格图来说,每一行配对一列,然后检查当前的C是否可行。

那么如何求出ab才能使得针对C的check变得充分呢?
还记得之前我们看到过这题的第二个tag是费用流,所有我们不难想到利用aCb来进行建图:

S向每个男生连一条流量为1,费用为0的边
每个男生向每个女生连一条流量为1费用为aCb的边
每个女生向T连一条流量为1,费用为0的边

然后对于这个网络跑最大费用最大流,显然最大流一定能跑满n,这就保证了方案的可行性,然后在check一下费用是否大于等于0来作为check函数的返回值

然后就是注意一下由于本题是对一个小数进行二分,所以精度不能给得太高,我设的eps是1e-8,这样右边界转化为整数就是1e14左右,还是可以接受的,再有就是要注意dinic时是否忘记修改流量或者spfa时忘记修改队列状态而导致的TLE,RE等一系列问题半个小时的血泪教训

最短路只写dijkstra导致spfa丢三落四导致的

#include<bits/stdc++.h>
const int N=505;
const int ID=105;
const int inf=1e9;
const double eps=1e-8;
using namespace std;
int n,m,e_cnt=1,S,T;
int head[N],a[N][N],b[N][N];
struct Edge{
int to,nxt,fl;
double w;
}e[N*N];
void add(int x,int y,int fl,double w)
{
e[++e_cnt]=(Edge){y,head[x],fl,w};head[x]=e_cnt;
e[++e_cnt]=(Edge){x,head[y],0,-w};head[y]=e_cnt;
//<<e_cnt<<"="<<x<<" "<<y<<" "<<fl<<" "<<w<<"\n";
}
double dis[N];
int vis[N],flow[N],dl[N];
int pre[N];
void init()
{
for(int i=S;i<=T;i++)
{
dis[i]=-inf;
flow[i]=vis[i]=pre[i]=0;
}
}
queue<int> Q;
bool spfa(int s,int t)
{
init();
dis[s]=0;flow[s]=inf;
Q.push(s);
while(!Q.empty())
{
int u=Q.front();Q.pop();
dl[u]=0;
if(++vis[u]>n)continue;
//<<u<<" "<<dis[u]<<"\n";
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to,fl=e[i].fl;
//<<v<<"="<<fl<<" ="<<dis[v]<<" "<<dis[u]+e[i].w<<"\n";
if(fl&&dis[v]<dis[u]+e[i].w)
{
dis[v]=dis[u]+e[i].w;
flow[v]=min(flow[u],fl);
pre[v]=i;
if(!dl[v])
{
dl[v]=1;Q.push(v);
}
}
}
//<<"\n";
}
return flow[t];
}
bool dinic()
{
double ans=0;
while(spfa(S,T))
{
int now=T,fl=flow[T];
ans+=1.0*fl*dis[T];
while(now!=S)
{
int id=pre[now];
e[id].fl-=fl;e[id^1].fl+=fl;
//<<now<<" ";
now=e[id^1].to;
}
//<<"="<<1.0*fl*dis[T]<<"\n";
}
//<<ans<<"\n";
return ans>=0;
}
void build(double k)
{
for(int i=1;i<=n;i++)
{
add(S,i,1,0);add(i+ID,T,1,0);
for(int j=1;j<=n;j++)
{
add(i,j+ID,1,1.0*a[i][j]-1.0*k*b[i][j]);
}
}
}
void INIT()
{
e_cnt=1;
for(int i=S;i<=T;i++)
{
head[i]=0;
}
}
bool check(double k)
{
INIT();
build(k);
return dinic();
}
void work()
{
cin>>n;
S=0,T=ID<<1|1;
for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){scanf("%d",&a[i][j]);}}
for(int i=1;i<=n;i++){for(int j=1;j<=n;j++){scanf("%d",&b[i][j]);}}
double l=0,r=1000000,ans=0;
while(l+eps<r)
{
double mid=(l+r)/2;
if(check(mid))
{
ans=mid;
l=mid;
}
else
{
r=mid-eps;
}
//cout<<l<<r<<""
}
printf("%lf",ans);
}
int main()
{
//freopen("dance.in","r",stdin);
//freopen("dance.out","w",stdout);
work();
return 0;
}
posted @   liuboom  阅读(22)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 实操Deepseek接入个人知识库
· 易语言 —— 开山篇
· Trae初体验
点击右上角即可分享
微信分享提示