这题是真的有点难度,主要在于他的时间复杂度卡的真的特别紧,属于是二分法,前缀和,尺取法,单位元素讨论齐上阵才能够AC,有一点出错了就会TLE,我们来慢慢看思路
二分:实际上这个二分关系真的非常邪门,对于第K大的元素x,那说明他前面起码有k个元素是不小于x的,这就是二分关系(很神奇吧),具体成代码就是
if(bsum >= m)
{
ansout = mid;
left = mid + 1;
}
else
{
right = mid - 1;
}
结合代码来看就很明显了,我们不断二分,那么最后那个ans就是正好“大于等于”的元素,就是我们需要求的
单位元素考虑:众所周知,对于这种“全区间”类型的问题,我们一般考虑使用单个元素来看他所对应的区间,用这种方式来减少原本是On2甚至是更高的复杂度。这里假设我们要看所有大于k的区间的第k大元素有多少个大于mid,那对于每个元素:我们只需要枚举右端点,之后每个右端点枚举左端点看有多少个元素大于等于mid就可以了
前缀和:区间问题用前缀和简化是老生常谈的问题,这里当然也不例外——使用pren前缀和数组来记录大于等于mid的数量,就可以很方便的O1拿到对应区间内大于mid的元素数量
不过有一点很神奇就是我们的前缀和一般是使用类似于1—based的方法:也就是pern[n]是从开始到第n - 1个元素,结合代码就是
for(int r = 1;r <= n;r++)
{
pren[r] = pren[r - 1] + (num[r - 1] >= mid);//前缀和都忘了,这里应该是num[r - 1]而不是num[r]
}
这样主要是为了能拿到第一个元素,否则lp就要成为-1了,这肯定不对
尺取法:但是为了进一步减少时间:我们知道:如果一个区间可行,那么包含他的子区间肯定就更可行了,也就是说:对于每个右端点——其保底就有上一个元素的贡献,只可能更大不可能更小
文字描述比较抽象,我们来结合代码来看看:
for(int rp = k;rp <= n;rp++)
{
int lp = 0;
while(pren[rp] - pren[lp] >= k && lp < rp)
{
lp++;
bsum++;
}
}
也就是贡献lp(左指针)可以反复利用,类似于滑动窗口一样,避免了多次遍历左指针造成的时间浪费
反正还是希望大家保持一定的刷题水平,不要放弃也不要强行越级,我的acm之路应该还没开始就快要结束了,希望大家可以走得更远
AC代码:
/*题目描述
爱丽丝得到了一个包含 N 个数字的数组 A1..N。
现在爱丽丝想按照以下规则用参数 K 构建一个数组 B:
初始时,数组 B 为空。考虑数组 A 中的每个区间。如果这个区间的长度小于 K,就忽略这个区间。否则,在这个区间中找到第 K 大的数字,并将这个数字添加到数组 B 中。
实际上爱丽丝并不关心数组 B 中的每个元素。她只想知道数组 B 中第 M 大的元素。请你帮她找到这个数字。
输入描述
第一行是测试用例的数量。
对于每个测试用例:
第一行包含三个正整数 N (1 ≤ N ≤ 10⁵);K (1 ≤ K ≤ N);M。
第二行包含 N 个数字 Aᵢ (1 ≤ Aᵢ ≤ 10⁹)。
保证 M 不大于数组 B 的长度。
输出描述
对于每个测试用例,输出一行,包含数组 B 中第 M 大的元素。*/
#include<bits/stdc++.h>
#define int long long
using namespace std;
signed main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T;
cin >> T;
for(int t = 0;t < T;t++)
{
int n,k,m;
cin>>n>>k>>m;
vector<int> num(n,0);
for(int r = 0;r < n;r++)
{
cin>>num[r];
}
int left = 0,right = *max_element(num.begin(),num.end());
int ansout;
while(left <= right)
{
int mid = (left + right)/2;
vector<int> pren(n + 3,0);
for(int r = 0;r <= n;r++)
{
pren[r] = pren[r - 1] + (num[r] >= mid);
}
int bsum = 0;
int lp = 0;
for(int rp = k - 1;rp < n;rp++)
{
while(pren[rp] - pren[lp] >= k && lp < rp)
{
lp++;
}
bsum += lp;
}
if(bsum >= m)
{
ansout = mid;
left = mid + 1;
}
else
{
right = mid - 1;
}
}
cout<<ansout<<endl;
}
return 0;
}