二分中位数

一道比较有意思的二分题

题面

给定 n 个整数,其中第 i 个整数是 ai。你需要从中选出若干个整数,使得所有相邻的整数至少被选择了一个。
你需要最大化你选择的卡片的中位数,并输出这个最大值。这里的中位数是 n 个整数中从小到大第 n2 个元素。

n2×105,0ai109

思路

不是那么显然的二分。考虑一个数 x 能不能成为中位数,我们每次选择一个数,如果其值大于 x 那么我们给 cnt++,否则我们令 cnt

最后看如果 cnt>0,那么存在比 x 大的中位数;如果 cnt<0,说明存在比 x 小的中位数;只有当 cnt=0 时,我们才能保证 x 可以成为中位数(因为就算我们没有选到 x ,原题中没有对选择加限制,所以我们可以选择它使其成为中位数)。

假设我们用 dp 求出最大的 cntmax 和最小的 cntmin,我们不难发现当 cntmin0cntmax 时, x 一定可以是中位数,因为我们可以保证 cnt 的取值连续。

然后我们又不难注意到,随着 x 的增大,cntmincntmax 的取值单调不升,所以可以考虑二分。

因为两者都单调不降且我们要找的是最大中位数,所以我们只需要找到从右到左第一个 cntmax>=0 的即可(一定有解,第一个肯定可以是中位数,再左边的 cntmin 肯定比其 cntmin 要大,所以其 cntmin 一定小于等于 0)。

然后就二分做了。

Code

展示两种种二分思路。
最后一个 <0 的下一个:

int n,a[200005],b[200005],f[2][2];
bool check(int x){
f[0][0]=0,f[1][0]=0;
int o=1;
for(int i=1;i<=n;i++,o^=1){
f[0][o]=f[1][o^1];
f[1][o]=max(f[0][o^1],f[1][o^1])+(a[i]>x?1:-1);
}
return max(f[0][o^1],f[1][o^1])<=0;
}
int main(){
n=read();
for(int i=1;i<=n;i++)
b[i]=a[i]=read();
sort(b+1,b+1+n);
int l=1,r=n;
while(l<=r){
int mid=(l+r)>>1;
if(check(b[mid]))r=mid-1;
else l=mid+1;
}
//此时 r 指向的是最后一个 <0 的,那么 l 指向的就是第一个 >= 0的
cout<<b[l];
}

或者保险一点直接找答案的:

//相同代码略
int main(){
//...
while(l<r){
int mid=(l+r)>>1;
if(check(b[mid]))r=mid;//让 r 指向最后一个满足条件的
else l=mid+1;
}
cout<<b[r];
}

其实可以不用离散的,因为从右到左第一个可以的一定在原数组里嘛(假设不在,那么说明有一个更大的在数组里面的和这个值等效,那我肯定选更大的在数组里的对吧)。

int n,a[200005],f[2][2];
bool check(int x){
f[0][0]=0,f[1][0]=0;
int o=1;
for(int i=1;i<=n;i++,o^=1){
f[0][o]=f[1][o^1];
f[1][o]=max(f[0][o^1],f[1][o^1])+(a[i]>x?1:-1);
}
return max(f[0][o^1],f[1][o^1])<=0;
}
int main(){
n=read();
for(int i=1;i<=n;i++)
a[i]=read();
int l=0,r=1e9+3;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid))r=mid-1;
else l=mid+1;
}
cout<<l;
}

后记

这道题困扰了我挺久的,一开始听说可以用奇怪的二分写然后就在信息课上随便打了一个没有离散,乱写版二分,小样例过了直接交然后发现过了。非常疑惑,不理解这凑巧的二分为什么能过,然后自己想了挺久的,所以这里就算是记录一下证明过程吧(又浪费了下午为数不多的三节竞赛课中的两节,受不了)。

end:2023.10.6 16:10

本文作者:NBestの思潮

本文链接:https://www.cnblogs.com/NBest/p/17744674.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   NBest  阅读(23)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起