K-th Number二分+枚举区间的好方法---尺取法

该博客介绍了一种解决数组区间问题的巧妙方法——尺取法,结合二分查找来确定特定条件下的第K大数。文章通过示例解释了如何运用这种方法,并给出了关键的check函数实现。此外,还提供了完整的C++代码示例,帮助读者理解并应用这一算法。
摘要由CSDN通过智能技术生成
题目描述 
Alice are given an array A[1..N] with N numbers.
Now Alice want to build an array B by a parameter K as following rules:
Initially, the array B is empty. Consider each interval in array A. If the length of this interval is less than K, then ignore this interval. Otherwise, find the K-th largest number in this interval and add this number into array B.
In fact Alice doesn't care each element in the array B. She only wants to know the M-th largest element in the array B. Please help her to fi nd this number.
输入描述:
 The first line is the number of test cases. For each test case, the first line contains three positive numbers N(1≤N≤105);K(1≤K≤N);M.
 The second line contains N numbers Ai(1≤Ai≤109).
It's guaranteed that M is not greater than the length of the array B.
输出描述:
For each test case, output a single line containing the M-th largest element in the array B.
示例1
输入
复制
2
5 3 2
2 3 1 5 4
3 3 1
5 8 2
输出
复制
3
2

这道题的题意就是给一个数组,求这个数组中所有长度大于等于k的区间中的所有第K大数组成集合中的第K大数

说实话我以为是分治,然后没有做出来,于是我开始看题解。
学到了一种比较巧妙的枚举区间的方法----尺取法

先来说说这道题怎么写:
对于答案x,必定存在大于等于k个区间的第K大数大于x,因此我们可以二分答案,二分出x的值,check()函数才是关键
check函数中我们令左右指针leftright同时指向一开始的起点,然后右指针开始向右扩展,每一扩展到一个大于x的数便累加到记录当前区间比x大的数的个数num变量上。当num的值正好等于k时,意味着这是一个第k大数大于等于x的区间,因此右指针继续不论怎么向右移动,都是合法区间。因此,记录区间个数的变量cnt可以直接加上n-right+1。这个时候再向右移动左区间直到当前区间里面的大于x的数小于num,在此过程中,左区间每移动一次,贡献出的不同区间数也是n-right+1.代码长这个样子:

for(LL left = 1,right = 1;right <= n;right ++){
		if(a[right] >=x) num++;
		if(num == k){
			cnt += n - right + 1;
			while(a[left] < x){
				cnt += n - right + 1;
				left ++ ;
			}
			left ++;
			num --;
		}
	}

总的代码

LL T,n,m,k,a[N];
vector<int> ans;

bool check(int x){ //x为二分估计的答案值
	LL cnt = 0; //第k大数大于等于x的区间个数
	LL num = 0; //当前尺取的区间中大于等于x的数的个数
	for(LL left = 1,right = 1;right <= n;right ++){
		if(a[right] >=x) num++;
		if(num == k){
			cnt += n - right + 1;
			while(a[left] < x){
				cnt += n - right + 1;
				left ++ ;
			}
			left ++;
			num --;
		}
	}
	if(cnt >= m) return 1;
	return 0;
}

void solve(){
    LL l = 1, r = 0x3f3f3f3f;
    while(l<r){
    	LL mid = l+r >> 1;
    	if(check(mid)) l = mid+1;
    	else r = mid;
    }
    printf("%lld\n",l-1ll);
}
int main(){
    T = read();
    while(T--){    
        n = read(),k = read(),m = read();
        rep(i,1,n) a[i] = read();
        solve();
    }
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值