【题解】Ples [COCI2011]

依旧是没有传送门,只有提供了数据官网

【题目描述】

\(N\) 个汉子和 \(N\) 个妹纸一起参加舞会,跳舞时只能是一个汉子一个妹纸配对,现在给出每个人的身高,任意一个人都只想和比 \(\text{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\)


【分析】

乍一看:我 \(c\),开局就上送分板子题。

恩恩?不对不对,光是建图就要体验 \(n\) 方卡百万。

再一看:\([1500,2500]\) 迅速发现了隐藏数据范围 \(h \leqslant 1000\),反手就是一个网络流

这应该是 \(\text{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\n",maxflow);
    fclose(stdin);
    fclose(stdout);
    return 0;
}
02-01 01:56