P3705 [SDOI2017] 新生舞会
P3705 [SDOI2017] 新生舞会
题目描述
学校组织了一次新生舞会,Cathy 作为经验丰富的老学姐,负责为同学们安排舞伴。
有 \(n\) 个男生和 \(n\) 个女生参加舞会,一个男生和一个女生一起跳舞,互为舞伴。
Cathy 收集了这些同学之间的关系,比如两个人之前认识没,计算得出 \(a_{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=\frac {a'_1+a'_2+...+a'_n}{b'_1+b'_2+...+b'_n}\)
Cathy 希望 \(C\) 值最大。
输出格式
一行一个数,表示 \(C\) 的最大值。四舍五入保留 \(6\) 位小数,选手输出的小数需要与标准输出相等。
对于 100% 的数据,\(1\le n\le 100,1\le a_{i,j},b_{i,j}\le10^4\)\
Solution:
拿到题目的第一眼:完了我不会,再打开题目标签一看,
二分?费用流?我直接悟了
二分肯定是针对C来进行二分了,然后我们先推一下式子:
C=\(\frac{\sum{a}}{\sum{b}}\)
\(C \times \sum{b} = \sum{a}\)
\(\sum{a}-C \times \sum{b}=0\)
然后我们再来看看x是怎么选的:
“有 \(n\) 个男生和 \(n\) 个女生参加舞会,一个男生和一个女生一起跳舞,互为舞伴”
也就是说,对于这整个网格图来说,每一行配对一列,然后检查当前的C是否可行。
那么如何求出\(\sum{a}\)和\(\sum{b}\)才能使得针对C的check变得充分呢?
还记得之前我们看到过这题的第二个tag是费用流,所有我们不难想到利用\(a-C*b\)来进行建图:
S向每个男生连一条流量为1,费用为0的边
每个男生向每个女生连一条流量为1费用为\(a-C*b\)的边
每个女生向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;
}

浙公网安备 33010602011771号