codeforces1486D(二分答案,用+1-1判断是否是区间的中位数)
Problem - D - Codeforces
解题思路
要找到长度不小于k的子序列中最大的中位数,可以用二分答案的方法,可是要怎么个二分法呢,二分中位数吗,可是中位数怎么二分,设二分的数是x,难道是确定是否有子序列的中位数为x吗,如果是这样的话就不知道该怎么二分了,而且为了确定是不是中位数复杂度就爆了,因为很容易想到的暴力方法就是枚举每个区间然后排序,复杂度非常高,所以就可以用将大于等于x的数变成1,小于等于x的数变成-1,如果区间和是0 or 1,那么x就是这个区间的中位数,如果大于1就说明该区间比x比该区间的中位数小,小于0的话就是比区间的中位数大,这样就可以用二分了,要是x比所有区间的中位数都大的话就让x变小,反之变大,边界的时候就是答案了,那如何确定x比所有的区间中位数都大或者都小呢,可以用数组MIN保存所有的前缀里最多能删除的小于x的个数,如果区间1到n中大于等于x的数有s+10个,小于x的数有s+18个,所以他们的数量差是8个,而区间1到m(m<n)中比大于等于x的数和小于x的数的数量差最大时为9(小于x的数更多)分别时t和t+9,如果把这个区间的数都删掉,那么区间1到n中大于x的数的个数就是s+10-t,而小于x的数的个数就是s+18-t-9=s+9-t,他们的差值就变成了1,此时x就成了中位数,用这种方法就可以确定x是否比所有区间的中位数都大了。
代码
#include<bits/stdc++.h>
#define all(x) x.begin(),x.end()
#define int long long
#define maxn 200005
#define endl "\n"
using namespace std;
int k;
vector<int>s(maxn),MIN(maxn);//不能够确定这个数是不是中位数
//但是能够确定这个数是在数列的左边还是右边,s大于0就是在左边,小于0就是在右边
int check(vector<int>&a,int x){//这段代码的实际意义是人为制造单调性
int l=a.size()-1;
for(int i=1;i<=l;i++){
s[i]=s[i-1];
if(a[i]>=x)s[i]++;
else s[i]--;
MIN[i]=MIN[i-1];
MIN[i]=min(MIN[i],s[i]);
}
for(int i=k;i<=l;i++){
if(s[i]-MIN[i-k]>=1)return 1;//MIN表示最多可以去掉的小于x的个数
}
return 0;
}
void solve(){
int n;
cin>>n>>k;
vector<int>a(n+1);
for(int i=1;i<=n;i++){
cin>>a[i];
}
int l=0,r=maxn;
while(1){
if(r-l<3){
for(int i=r;i>=l;i--){
if(check(a,i)==1){
cout<<i<<endl;
return;
}
}
}
int mid=(l+r)/2;
if(check(a,mid)){
l=mid;
}
else{
r=mid;
}
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int test=1;
while(test--){
solve();
}
return 0;
}
总结
对中位数的理解:
首先是为什么可以用大于x的数+1,小于的就-1来表示区间的中位数是否是x呢,因为对于中位数来说,大于他的数和小于他的数具体是什么没有意义,所以可以直接将数值进行转换,中位数的本质就是比中位数大的数的个数等于比中位数小的个数
对二分答案的理解:
不要拘泥于答案是不是符合条件,要寻找小于最终答案有什么共性,和大于最终答案有什么共性
对前缀和的理解:
前n个的和就是对前n个特性的累加,减去前缀m,就是将前n个的特性区间前m个的特性