King of Range(区间个数,双指针,单调队列/ST表)

本文探讨了一种优化问题,如何在给定数组中高效地计算满足特定条件的区间数量。通过介绍单调队列和ST表两种方法,对比了它们在解决n*n查询问题时的时间复杂度,以及在实际代码实现中的应用场景。重点在于利用双指针技巧将时间复杂度降低到O(n)以提升效率。
摘要由CSDN通过智能技术生成

https://ac.nowcoder.com/acm/contest/11256/K


题意
给定长度为 n n n 的数列 a [ ] a[] a[] m m m 次询问。
每次询问给定一个值 k k k,问一共有多少个不同的区间满足,其元素 最大值 - 最小值 > k

1 ≤ n ≤ 1 0 5 , 1 ≤ m ≤ 200 1≤n≤10^5,1≤m≤200 1n105,1m200
1 ≤ a i ≤ 1 0 9 1≤a_i≤10^9 1ai109
1 ≤ k ≤ 1 0 9 1≤k≤10^9 1k109

思路
如果只有一个询问的话,可以遍历每个位置 i,把该位置作为区间左端点,向右二分找第一个满足的右端点 j,该位置和后面的所有位置都是满足的,区间个数贡献 n - j + 1

但时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn),需要想办法优化。

假设有一个合法区间的左端点为 l,右端点为 r,当 l 右移的时候,区间中就少了几个数,可能这个区间就不合法了。为了让区间重新合法,右端点就需要右移。

所以,当左端点从左往右走时,为了构成合法区间,右端点是单调往后走的。

那么就可以用双指针来优化,时间复杂度 O ( n ) O(n) O(n)

从前往后遍历每个位置 i,把当前位置作为区间左端点,往后找第一个合法的右端点 j,该位置和后面的所有位置都可以作为右端点,能够贡献 n-j+1 个区间。
判断区间是否合法可以用 两个单调队列分别维护一段移动区间的最大值和最小值,也可以用 ST表直接求区间最大值最小值

Code:
法1:单调队列维护移动区间最值,O(n)

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define endl '\n'

const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];
int q1[N], q2[N];

signed main(){
	Ios;
	cin >> n >> m;
	for(int i=1;i<=n;i++) cin >> a[i];
	
	while(m --)
	{
		int x; cin >> x;
		int st1 = 0, ed1 = -1; //最小值,递增 
		int st2 = 0, ed2 = -1; //最大值,递减 
		
		int ans = 0;
		int j = 0;
		for(int i=1;i<=n;i++)
		{
			while(st2 > ed2 || st1 > ed1 || a[q2[st2]] - a[q1[st1]] <= x) //往后找到第一个满足位置(当队列中没有值或者还没找到时继续找) 
			{
				if(j == n) break; //如果到最后一个位置了还没找到,退出 
				j ++;
				while(ed1 >= st1 && a[q1[ed1]] >= a[j]) ed1 --;
				while(ed2 >= st2 && a[q2[ed2]] <= a[j]) ed2 --;
				q1[++ed1] = j;
				q2[++ed2] = j;
			}
			if(st2 <= ed2 && st1 <= ed1 && a[q2[st2]] - a[q1[st1]] > x) //如果找到了 
				ans += n - j + 1;
				
			if(q1[st1] == i) st1 ++; //把当前位置从队列中清除 
			if(q2[st2] == i) st2 ++;
		}
		cout << ans << endl;
	}
	
	return 0;
}

法2:ST表维护任意区间最值,O(nlogn)

需要注意的是,这个题目中需要调用 query 的地方很多,而每次调用该函数都会调用一次 log() 函数,这个函数是比较耗时的,在这个题目中就超时了。

解决办法:
先把所有区间长度的 log 值预处理出现,存到数组中,后面调用直接查数组即可。总的区间长度为 1e5,那么子区间长度最多也 1e5 个。
另外可以用内置函数 __builtin_log() 函数替换 log(),这个快一点。

#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define endl '\n'

const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];
int q1[N], q2[N];
int Fmin[N][30], Fmax[N][30];
double Log[N], Log2 = log(2);

void ST()
{
	for(int i=1;i<=n;i++) Fmin[i][0] = Fmax[i][0] = a[i];
	
	int k = log(n) / log(2);
	for(int j=1;j<=k;j++)
		for(int i=1;i<=n;i++){
			Fmin[i][j] = min(Fmin[i][j-1], Fmin[i + (1 << j - 1)][j - 1]);
			Fmax[i][j] = max(Fmax[i][j-1], Fmax[i + (1 << j - 1)][j - 1]);
		}
}

int query_max(int l, int r){
	int k = Log[r - l + 1] / Log2;
	return max(Fmax[l][k], Fmax[r - (1 << k) + 1][k]);
}

int query_min(int l, int r){
	int k = Log[r - l + 1] / Log2;
	return min(Fmin[l][k], Fmin[r - (1 << k) + 1][k]);
}

signed main(){
	Ios;
	cin >> n >> m;
	for(int i=1;i<=n;i++) cin >> a[i];
	for(int i=1;i<=n;i++) Log[i] = log(i);
	
	ST();
	
	while(m --)
	{

		int x; cin >> x;
		int ans = 0;
		int j = 1;
		for(int i=1;i<=n;i++)
		{
			while(j < n && query_max(i, j) - query_min(i, j) <= x) j ++; //找到后面第一个满足的位置 
			
			if(query_max(i, j) - query_min(i, j) > x) //如果找到了 
				ans += n - j + 1;
		}
		cout << ans << endl;
	}
	
	return 0;
}

之前遇这种区间最值问题都是直接 ST表,单调队列练的少,这道题竟然被卡了。

只要需要求区间最值的区间是一个位置一个位置移动的,那么就可以用单调队列来维护最值,处理这种移动的区间最值问题。时间复杂度 O(n),比 ST表 的 O(nlogn) 效率更高。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值