杭电多校第10场HDU6701.Make Rounddog Happy(线段树)

题目大意:

给定一个长度为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;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值