原题:
题意:
给你一个长度为N的正整数组A,对于这个数组的所有子区间,若长度小于k则不管它,若长度大于等于k则取第k大放入数组B
问你B中第M大的数是谁
一眼序列分治,然而没思路
数据结构?能想到从大到小排序,然后小于第i个数的都视为1,用数据结构维护第i个数在多少个区间是第k大
然后就没有然后了……
序列分治和数据结构自闭了两个小时,最后才想起来试试别的思路
比如DP或二分什么的
终于灵稽一动
答案满足二分单调性
二分的答案m越大,[m+1,n]中的数作为第k大的区间的总数量就越大
那么二分一个答案m,问题转化为求[m+1,n]中的数作为第k大的区间的总数量
因为只需要[m+1,n]中的任意一个数作为第k大,而[1,m]的数没有任何用,可以无视
那么可以把大于m的数看成1,小于等于m的数k看成0
问题转化为统计有多少个子区间,使得区间内有k个1
只需要O(n)复杂度,可以双指针,左边指针i每次加1,如果经过1就让区间中1的减1,如果1的个数小于k,就让右边的指针j往右跑到刚好等于k为止,把n-j+1统计到结果即可
注意二分的写法,容易写错
代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 #include<cmath> 6 using namespace std; 7 #define LL long long 8 int rd(){int z=0,mk=1; char ch=getchar(); 9 while(ch<'0'||ch>'9'){if(ch=='-')mk=-1; ch=getchar();} 10 while(ch>='0'&&ch<='9'){z=(z<<3)+(z<<1)+ch-'0'; ch=getchar();} 11 return z*mk; 12 } 13 int n,o; LL m; 14 int a[110000]; 15 int b[110000]; 16 LL cclt(int x){ 17 for(int i=1;i<=n;++i) b[i]=(a[i]>=x); 18 LL bwl=0; 19 for(int i=1,j=0,k=0;i<=n && j<=n;++i){ 20 for(;k!=o && j<n;) k+=b[++j]; 21 if(j>n || (j==n && k!=o)) break; 22 bwl+=n-j+1; 23 k-=b[i]; 24 } 25 return bwl; 26 } 27 int bnrsch(int x,int y){ 28 int l=x,r=y,md; 29 while(l+1<r){ 30 md=(l+r)>>1; 31 (cclt(md)>=m ? l : r)=md; 32 } 33 return cclt(r)>=m ? r : l; 34 } 35 int main(){ 36 //freopen("ddd.in","r",stdin); 37 int T; cin>>T; 38 while(T --> 0){ 39 cin>>n>>o>>m; 40 int mx=0,mn=1000000007; 41 for(int i=1;i<=n;++i){ 42 a[i]=rd(); 43 mx=max(mx,a[i]); 44 mn=min(mn,a[i]); 45 } 46 printf("%d ",bnrsch(mn,mx)); 47 } 48 return 0; 49 }