【题解】Ples [COCI2011]
【题目描述】
(N) 个汉子和 (N) 个妹纸一起参加舞会,跳舞时只能是一个汉子一个妹纸配对,现在给出每个人的身高,任意一个人都只想和比 ( ext{ta}) 高(矮)的人跳,身高一样的人都不想与对方跳。输出满足人们愿望的前提下可组成的最多对数。.
【输入】
第一行一个数 (N) 表示汉子和妹纸的个数。
第二行 (N) 个整数,其绝对值表示 (N) 个汉子的身高 (h),(h) 为正数表示他想与比自己高的人跳,负数则相反。
第三行 (N) 个整数,表示妹纸,其它与第二行相同。
【输出】
一个数,表示组成的最多对数。.
【样例】
样例输入:
1
-1800
1800
样例输出:
0
样例输入:
1
1700
-1800
样例输出:
1
样例输入:
2
-1800 -2200
1900 1700
样例输出:
2
【数据范围】
(100 \%:) (1 leqslant N leqslant 100000,) (1500 leqslant h[i] leqslant 2500)
【分析】
恩恩?不对不对,光是建图就要体验 (n) 方卡百万。
再一看:([1500,2500]) 迅速发现了隐藏数据范围 (h leqslant 1000),反手就是一个网络流。
这应该是 ( ext{COCI2011}) 写起来最爽的一道题了吧。。。不过正解貌似是贪心。
由于本题只关注相对大小,可以把输入的每个 (h) 都减去 (1500) 再加 (1),映射到 ([1,1001]) 里面去,然后记录在每一种身高下汉子、妹纸的个数。
(A[h][1]) 表示高度为 (h) 且想要配偶比自己高的汉子数量,
(A[h][0]) 表示高度为 (h) 且想要配偶比自己矮的汉子数量;
(B[h][1]) 表示高度为 (h) 且想要配偶比自己高的妹纸数量,
(B[h][0]) 表示高度为 (h) 且想要配偶比自己矮的妹纸数量。
对每个高度建四个点,分别表示上述加黑字体的四种人,超源连向所有汉子,妹纸连向所有超汇,容量设为人种数量。
(1001^2) 枚举所有高度配对:
对于 (h_i>h_j),(0) 汉子与 (1) 妹纸连一条容量为 (min(A[h_i][0],B[h_j][1])) 的边。
对于 (h_i<h_j),(1) 汉子与 (0) 妹纸连一条容量为 (min(A[h_i][1],B[h_j][0])) 的边。
然后随便背个最大流板子就 (ok) 啦。
建边数量为:((1001^2+1001*4)*2=1006005*2) 。
【Code】
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#define Re register int
using namespace std;
const int N=4050,M=1006050,inf=2e9;
int n,x,o=1,T,st,ed,maxflow,A[N>>2][2],B[N>>2][2],head[N];
struct QAQ{int to,next,flow;}a[M<<1];
inline void add_(Re x,Re y,Re flow){a[++o].flow=flow,a[o].to=y,a[o].next=head[x],head[x]=o;}
inline void add(Re x,Re y,Re flow){add_(x,y,flow),add_(y,x,0);}
inline void in(Re &x){
int f=0;x=0;char c=getchar();
while(c<'0'||c>'9')f|=c=='-',c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
x=f?-x:x;
}
int h,t,Q[N],cur[N],dis[N];
inline int bfs(Re st,Re ed){
for(Re i=0;i<=ed;++i)cur[i]=head[i],dis[i]=0;
h=1,t=0,dis[st]=1,Q[++t]=st;
while(h<=t){
Re x=Q[h++],to;
for(Re i=head[x];i;i=a[i].next)
if(a[i].flow&&!dis[to=a[i].to]){
dis[to]=dis[x]+1,Q[++t]=to;
if(to==ed)return 1;
}
}
return 0;
}
inline int dfs(Re x,Re flow){
if(!flow||x==ed)return flow;
Re tmp=0,to,f;
for(Re i=cur[x];i;i=a[i].next){
cur[x]=i;
if(dis[to=a[i].to]==dis[x]+1&&(f=dfs(to,min(flow-tmp,a[i].flow)))){
a[i].flow-=f,a[i^1].flow+=f,tmp+=f;
if(tmp==flow)break;
}
}
return tmp;
}
inline void Dinic(Re st,Re ed){while(bfs(st,ed))maxflow+=dfs(st,inf);}
int main(){
// freopen("ples.in","r",stdin);
// freopen("ples.out","w",stdout);
in(T),n=1001,st=n<<2|1,ed=st+1;
for(Re i=1;i<=T;++i){
in(x);
if(x>0)++A[x-1500+1][1];//汉子想要妹纸比自己高
else ++A[-x-1500+1][0];//汉子想要妹纸比自己矮
}
for(Re i=1;i<=T;++i){
in(x);
if(x>0)++B[x-1500+1][1];//妹纸想要汉子比自己高
else ++B[-x-1500+1][0];//妹纸想要汉子比自己矮
}
for(Re i=1;i<=n;++i){//超源连汉子
if(A[i][0])add(st,i,A[i][0]);
if(A[i][1])add(st,i+n,A[i][1]);
}
for(Re i=1;i<=n;++i){//妹纸连超汇
if(B[i][0])add(i+(n<<1),ed,B[i][0]);
if(B[i][1])add(i+(n<<1)+n,ed,B[i][1]);
}
for(Re i=1;i<=n;++i){//汉子连妹纸
if(A[i][0])
for(Re j=1;j<i;++j)
if(B[j][1])add(i,j+(n<<1)+n,min(A[i][0],B[j][1]));//必须要两厢情愿才连边
if(A[i][1])
for(Re j=i+1;j<=n;++j)
if(B[j][0])add(i+n,j+(n<<1),min(A[i][1],B[j][0]));
}
Dinic(st,ed);
printf("%d
",maxflow);
fclose(stdin);
fclose(stdout);
return 0;
}