-
题意:给出一个序列,问其存在多少个子序列(子序列肯定连续~)满足子序列中最大值与最小值的差大于k。
-
思路:ST表预处理 + 双指针维护区间。
-
解析:以下用\(maxv, minv\)表示该区间最大值和最小值,假设当遍历到区间[l, r - 1]时,\(maxv - minv \leq k\)说明该区间内不可能存在子区间满足条件,但当遍历到[l, r]区间时, \(maxv - minv > k\) 说明[l, r]区间满足条件, 并且[l, r + 1],[l, r + 2]...[l, n]一定都满足条件。
那么我们可以首先用ST表预处理出所有区间的\(maxv, minv\),然后利用双指针\(i\)为头指针,\(j\)为尾指针,如果当前区间\(maxv - minv \leq k\),那么尾指针\(j\)需要往后移动(有一些细节附加条件可以看代码),否则需要累加答案\(n - j + 1\)并且头指针\(i\)需要往后移动。
细节:此题在双指针维护区间过程中需要多次查询区间最大值和最小值,若log2()函数是直接用函数库的则会T掉,log2()函数时间复杂度为log(n),所以需要预处理\(log_2(1)\)~\(log_2(n)\)
-
代码:
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> using namespace std; typedef long long ll; const int N = 1e5 + 5; const int M = 20; int a[N], f[N][M], g[N][M]; //f(i, j)表示[i, i + 2^j - 1]最小值, g(i, j)表示最大值 int lg[N]; //log2(x) int n, m, k; void fun() //预处理log2()函数 { lg[0] = -1; for(int i = 1; i <= n; i++) lg[i] = lg[i >> 1] + 1; } void st_init() { for(int i = 1; i <= n; i++) { f[i][0] = a[i]; g[i][0] = a[i]; } for(int j = 1; j <= log2(n); j++) { for(int i = 1; i <= n + 1 - (1 << j); i++) { f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]); g[i][j] = max(g[i][j - 1], g[i + (1 << (j - 1))][j - 1]); } } } int query_min(int l, int r) { int len = r - l + 1; int k = lg[len]; return min(f[l][k], f[r - (1 << k) + 1][k]); } int query_max(int l, int r) { int len = r - l + 1; int k = lg[len]; return max(g[l][k], g[r - (1 << k) + 1][k]); } int main() { scanf("%d%d", &n, &m); for(int i = 1; i <= n; i++) scanf("%d", &a[i]); st_init(); fun(); while(m --) { scanf("%d", &k); ll num = 0; for(int i = 1, j = 1; i <= n; i++) { while( (i >= j || query_max(i, j) - query_min(i, j) <= k) && (j < n) ) j ++; if(query_max(i, j) - query_min(i, j) > k) //题目条件 num += ll(n - j + 1); } printf("%lld\n", num); } return 0; }