题目大意:
给定一个长度为N的数组,问有多少个子数组Array[L....R]满足max{Array[L....R]} - (R-L+1) <= K
且子数组中的数都不相同
思路:
这种求满足某种子段区间个数的题目好像都快成套路题了,答案说是RMQ+分治,那不就是线段树吗.....
本题用线段树就简介明了很多了。
首先,必不可少的就是枚举左端点L,那么我必须知道这个左端点最右端可能枚举的点R,这个从后往前O(n)的时间可以办到,记为pos[L]
然后,我们要求的就是有多少个小于pos[L]的R端点,满足K+R-L+1-maxv>=0,那么现在还有一个maxv不知道,我们也不知道怎么快速统计R,刚好,线段树都可以办到。
第一,线段树维护区间最大值,就可以得到maxv;然后,因为我们线段树递归是一个分治的过程,先走左子树,那么假设我们走到了线段树[xl,rl]这个区间,顺便查询出了当前前缀最大值maxv,那么如果K+xl-L+1-maxv>=0,也就是说区间左端点都满足条件了,那么这个区间[xl,rl]肯定都满足条件,继续向右子树递归;
如果K+xr-L+1-maxv<0,也就是说区间右端点都不满足条件,那么这个区间肯定都不满足条件,结束递归。
就这样,边查询的过程边计算有哪些区间满足条件,我们只要加上这些区间的长度就好了,是不是很方便~
细节看代码
#include <bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define pii pair<int,int>
#define sz(x) (int)(x).size()
#define all(x) (x).begin(),(x).end()
using namespace std;
typedef long long LL;
const int maxn = 3e5 + 35;
int T, n, k, a[maxn], pos[maxn], vis[maxn], MAX[maxn << 2];//线段树维护区间最大值
inline int read() {
int x = 0, f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -1;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
return x * f;
}
inline void write(LL x)
{
if (x < 0) putchar('-'), x = -x;
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
void BuildTree(int l, int r, int x) {
if (l == r) {
MAX[x] = a[r];
return;
}
int mid = (l + r) >> 1;
BuildTree(l, mid, x << 1);
BuildTree(mid + 1, r, x << 1 | 1);
MAX[x] = max(MAX[x << 1], MAX[x << 1 | 1]);
}
LL ans; int maxv;
//查询L到R这个区间内合法的R的个数
void QueryTree(int l, int r, int L, int R, int root, int v, int& maxv) {
//不相交的区间
if (l > R || r < L) return;
//完全包括的区间
if (l >= L && r <= R) {
//因为线段树先访问左子树再访问右子树
//当前区间为[l,r],当前枚举的左端点为v
//如果K+l-v+1-maxv>=0,那么说明这个区间[l,r]肯定都可以
if (k + l + 1 - v - max(maxv, MAX[root]) >= 0) {
ans += r - l + 1;
maxv = max(maxv, MAX[root]);
return;
}
//如果K+r-v+1-maxv<0,那么说明这个区间[l,r]肯定都不可以
if (l == r || k + r + 1 - v - maxv < 0) {
maxv = max(maxv, MAX[root]);
return;
}
}
int mid = (l + r) >> 1;
QueryTree(l, mid, L, R, root << 1, v, maxv);
QueryTree(mid + 1, r, L, R, root << 1 | 1, v, maxv);
}
LL solve() {
ans = 0;
for (int i = 1; i <= n; ++i) {
//枚举左端点
if (a[i] - 1 <= k) ans++;
maxv = a[i];
//pos[i]为当前满足条件的最远右端点
if (i + 1 < pos[i]) QueryTree(1, n, i + 1, pos[i] - 1, 1, i, maxv);
}
return ans;
}
int main() {
T = read();
while (T--) {
n = read(), k = read();
for (int i = 1; i <= n; ++i) a[i] = read(), vis[i] = n + 1;
pos[n] = n + 1; vis[a[n]] = n;
for (int i = n - 1; i > 0; --i) { //预处理L=i时,R的最大值,存在pos中
pos[i] = min(vis[a[i]], pos[i + 1]);
vis[a[i]] = i;
}
BuildTree(1, n, 1);
write(solve()), putchar('\n');
}
return 0;
}