原题洛谷查看CF1486D Max Median
题意
一个长度为n(1<=n<=2e5)的序列a,定义中位数为一段连续的数升序排序后中间的那个数。如果长度为偶数len,则中位数为第(len/2向下取整)个数。求序列a所有长度大于等于k(1<=k<=2e5)的连续子序列中,中位数的最大可能的取值。
思路
可以看出,答案具有单调性,因此可以使用二分答案。
那如何判断一个数能否成为一段区间的中位数呢?
观察一些序列的中位数,其实可以发现:在长度为n的序列中,小于中位数的数至多有个,至少有个,总有小于中位数的数小于大于等于中位数的数。一种很奇妙的方法:在二分一个数x时,将序列a中的数转化为1和-1存入mark数组中,即
再用一个数组s记录mark的前缀和(此处称作状态和,表示从1到i的状态和),此时对于区间i到j的状态和为,令其大于0即可!
选择枚举,当已知时,此时让尽量小即可,那就要再维护一个前缀最小值mi数组,即。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll z=200005;
ll n,k,mid,l,r,a[z],c[z],s[z],mi[z],ans;
bool check(ll x)
{
for(int i=1;i<=n;i++)
{
if(a[i]>=x)c[i]=1;
else c[i]=-1;
}//转化,大于就是1小于就是-1
for(int i=1;i<=n;i++)
{
s[i]=s[i-1]+c[i];//前缀和
mi[i]=min(mi[i-1],s[i]);//前缀和的前缀最小值
}
for(int i=k;i<=n;i++)
if(s[i]-mi[i-k]>0)return true;//大于0的时候,说明中位数在x之上,推进左边界
return false;//否则推进右边界
}
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>a[i];
r=max(r,a[i]);//找最大数,获取右边界,减少查询次数
}
while(l<=r)//二分
{
mid=(l+r)/2;
if(check(mid))ans=mid,l=mid+1;
else r=mid-1;
}
cout<<ans;
return 0;
}