Examples
input
5 3
1 2 3 2 1
output
2
input
4 2
1 2 3 4
output
3
题目大意
给定一个长度为n的序列a,求a的所有连续且长度大于等于k的子序列里中位数的最大值。
分析
由于1<=ai<=n,因此中位数的取值为1~n,如果已经确定x是一个中位数,那么中位数的最大值一定>=x,这就说明中位数的最大取值具有单调性,考虑用二分来做。设左端点为l,右端点为r,初始条件l=1,r=n,每次取mid=(l+r+1)>>1,问题的关键在于如何判断是否存在一个中位数>=mid,这里用到一个巧妙的做法,将所有小于mid的数标记为-1,大于等于mid的数标记为1,这样就得到了一个只包含1和-1的序列。如果一个区间的中位数>=mid,那么这个区间(转换为1和-1后的区间)的和一定>0,根据这个结论,还需要求一下前缀和,由于序列长度是大于等于k的,因此需要寻找所有长度大于等于k的区间是否有和是大于0的,这里还用到了前缀最小值进行优化。
时间复杂度O(nlog(n))
AC代码
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int a[N];
int b[N];
int n,k;
bool check(int mid)
{
for(int i=1;i<=n;i++)
{
if(a[i]>=mid) b[i]=1;
else b[i]=-1;
}
for(int i=1;i<=n;i++) b[i]+=b[i-1];
int flag=0;
int mn=0; //最小值
for(int i=k;i<=n;i++)
{
mn=min(mn,b[i-k]);
if(b[i]-mn>0)
{
flag=1;
break;
}
}
return flag;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
int l=1,r=n;
while(l<r)
{
int mid=(l+r+1)>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
cout<<l<<endl;
return 0;
}
注意
这里mid=(l+r+1)>>1是为了避免死循环,是由于位运算向下取整引起的。