题意:
给出一个长为N的序列,求出所有子序列中的第k大数(子序列长度必然大于等于k)并将其添加到一个新的序列中,求这个新序列的第m大为多少。
思路:
(本弱鸡打这场模拟赛时没有做出来,太惨了...)
首先,看榜过的人不是很多,加上这种 求第k大的数(肯定不会用到 主席数,树状数组),因为求的是所有子区间,加上又有多组样例。所以我就一直想子区间之间的关系,一开始想既然是第 k 大,那么长度为 k的子区间,就是求其区间最小值(单调队列)。然后再 希望通过某种 递推关系 去一次推出区间长度+1 情况的第k大值。但是没有想出来这种思路的解法(可能方向都错了)。
模拟赛打完后看了题解,觉得的确非常巧妙。题解是通过找到了 某个值 与第k大,以及与新形成序列 的m大 之间的关系,求得的。
比如 对于下面样例序列(答案为3)
5 3 2 2 3 1 5 4
首先我们知道最后新的序列组成 来源于原序列。我们对原序列排序 1 2 3 4 5 , 那么新序列的组成形式为 ( 1(a) , 2(b), 3(c) ,4 (d), 5(e) )//这里的a,b,c,d,e表示出现次数
若原序列中,所有长度不小于k的连续子序列中,第k大数不小于x,的子序列一共有ans个,那么x在所有第k大元素组成的数列中的位置排在 ans 个之后了
而对于 求解 ans个做法即时,首先找到一个区间从 l 开始第一个满足 x 为第 k 大的 r 位置(尺取法),然后对于其右边的长度部分 (n-r+1) , 如果有比 x 小,则对 x 排名没影响;
比x大,x排名就降低(就统计排在第k大,并且大于x的区间数)。所以这样就求出了 所有第k大数不小于x的子序列个数(子区间)。由于尺取是 O(N)
我们如果再遍历搜索就会到 O(N^2) , 所以我们再使用二分搜索 降到 O(N logN) 复杂度。
code:
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e5+7; const int inf = 0x3f3f3f3f; ll a[maxn]; ll b[maxn]; ll n,k,m; bool judge(int x){ int num=0,j=1; ll ans =0; int l=1,r=0; // 尺取法的两种写法 while(r<=n){ if(num<k){ if(a[r+1]>=x) num++; r++; }else{ if(num==k) ans += n-r+1; if(a[l]>=x) num--; l++; } } // for(int i=1;i<=n;i++){ // if(a[i]>=x) num++; // if(num==k){ // ans += n-i+1; // while(a[j]<x){ // ans += n -i +1; // j++; // } // num--; // j++; // } // } //小于或者等于 if(ans>=m) return true; //大于 else return false; } int main(){ int T; scanf("%d",&T); while(T--){ scanf("%lld %lld %lld",&n,&k,&m); memset(b,0,sizeof(b)); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); b[i] = a[i]; } sort(b+1,b+1+n); int size = unique(b+1,b+1+n) - (b+1); int l =1,r= size; while(l<=r){ int mid = (l+r)>>1; if(judge(b[mid])){ l = mid+1; }else{ r = mid-1; } } printf("%d ",b[l-1]); } return 0; }