链接:https://www.nowcoder.com/acm/contest/172/A
来源:牛客网
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld
题目描述
小N得到了一个非常神奇的序列A。这个序列长度为N,下标从1开始。A的一个子区间对应一个序列,可以由数对[l,r]表示,代表A[l], A[l + 1], ..., A[r]这段数。对于一个序列B[1], B[2], ..., B[k],定义B的中位数如下:
1. 先对B排序。得到新的序列C。
2. 假如k是奇数,那么中位数为。假如k为偶数,中位数为。
对于A的所有的子区间,小N可以知道它们对应的中位数。现在小N想知道,所有长度>=Len的子区间中,中位数最大可以是多少。
输入描述:
第一行输入两个数N,Len。
第二行输入序列A,第i个数代表A[i]。
输出描述:
一行一个整数,代表所有长度>=Len的子区间中,最大的中位数。
备注:
数据范围:
30%: n <= 200
60%: n <= 2000
另外有20%:不超过50个不同的数
100%:1<=Len<=n<=10^5, 1 <= a[i] <= 10^9
思路:
表示考场上一看到这道题就一脸懵逼
于是交了一发主席树(当然不是正解)
听完讲评才发现二分水过。。。
其实很简单
先输进来
然后开个新数组去重,从小到大排序
然后就可以二分了
我们每次二分出一个答案
判断一下
怎么判断呢?
我们先O(n)扫一遍
x[i]大于等于这个答案话,就赋值为1
否则为-1
然后跑前缀和
再从len开始枚举
在枚举时维护一个最小前缀和(当前位置的前缀和-最小前缀和=最大前缀和)
如果最大前缀和>0,就是满足的(看题,这就说明它可以是从小到大第k/2位)
反之就不满足
OK
代码:
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define rii register int i #define rij register int j using namespace std; int qzh[100005],x[100005],y[100005],z[100005],n,len; bool check(int bh) { int ltt=z[bh]; int minx=192690817; memset(qzh,0,sizeof(qzh)); for(rii=1;i<=n;i++) { if(x[i]>=ltt) { qzh[i]=1; } if(x[i]<ltt) { qzh[i]=-1; } if(i>=len) { minx=min(minx,qzh[i-len]); } qzh[i]+=qzh[i-1]; if(i>=len) { if(qzh[i]-minx>0) { return 1; } } } return 0; } int main() { cin>>n>>len; for(rii=1;i<=n;i++) { scanf("%d",&x[i]); y[i]=x[i]; } sort(y+1,y+n+1); int pre=0; for(rii=1;i<=n;i++) { if(y[i]==z[pre]) { continue; } pre++; z[pre]=y[i]; } // for(rii=1;i<=pre;i++) // { // cout<<z[i]<<" "; // } int l=1,r=pre; while(l!=r) { if(r-l==1) { if(check(r)==1) { l=r; } else { r=l; } break; } int mid=(l+r)/2; if(check(mid)==1) { l=mid; } else { r=mid; } } cout<<z[r]; }