HDU 5289 Assignment (尺取+RMQ)
Description
给出一段长度为\(n\)的序列,问最大值与最小值之差小于\(k\)的连续序列有多少个。
Input
第一行给出用例组数\(T\),对于每组用例,第一行给出\(n\)和\(k\),第二行给出\(n\)个数表示这个序列。\(1 \leqslant n \leqslant 10^5\)。
Output
对于每组用例输出一个整数,表示满足条件的序列数量。
Sample Input
2
4 2
3 1 2 4
10 5
0 3 4 5 2 1 6 7 8 9
Sample Output
5
28
Solution
满足条件的连续区间,典型的尺取问题。限制条件是上限,区间最大值和最小值之差要小于\(k\)。左端点依次推进,对于每个左端点,右端点向右找到能延伸的远位置,此时这段区间长度就这个左端点的贡献。区间最值可用ST快速查询。
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int INF = 0x7f7f7f7f;
const int N = 1e5 + 10;
int a[N];
int dpmax[N][20], dpmin[N][20], lg[N];
void ST(int n)
{
lg[0] = -1;
for (int i = 1; i <= n; i++)
{
lg[i] = ((i & (i - 1))) == 0 ? lg[i - 1] + 1 : lg[i - 1];
dpmax[i][0] = dpmin[i][0] = a[i];
}
for (int j = 1; j <= lg[n]; j++)
for (int i = 1; i + (1 << j) - 1 <= n; i++)
{
dpmax[i][j] = max(dpmax[i][j - 1], dpmax[i + (1 << (j - 1))][j - 1]);
dpmin[i][j] = min(dpmin[i][j - 1], dpmin[i + (1 << (j - 1))][j - 1]);
}
}
int rmq(int l, int r)
{
int k = lg[r - l + 1];
int maxx = max(dpmax[l][k], dpmax[r - (1 << k) + 1][k]);
int minx = min(dpmin[l][k], dpmin[r - (1 << k) + 1][k]);
return maxx - minx;
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
int n, k;
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) scanf("%d", a + i);
ST(n);
ll ans = 0;
for (int l = 1, r = 1; l <= n; l++)
{
while (r <= n && rmq(l, r) < k) r++;
ans += r - l;
}
printf("%lld\n", ans);
}
return 0;
}