• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

RomanLin

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

【区间最值线段树】Atcoder 408 F - Athletic

题目

https://atcoder.jp/contests/abc408/tasks/abc408_f

题意

输入 \(n,d,r(1 \leq n \leq 5 \times 10^5, 1 \leq d \leq n,1 \leq r \leq n)\) 和一个大小为 \((n-1)\) 的排列 \(a\)。下标从 \(1\) 开始。

选择一个 \(1~n\) 中的下标,作为起点 \(i\),每次跳跃,选择一个下标 \(j\),满足 \(a[j] \leq a[i] - d \text{ 且 } 1 \leq |i-j| \leq r\)。重复上述步骤,直至无法跳跃。

输出最大跳跃次数。

题解

从下标 \(i\) 向下标 \(j\) 跳跃,首先需要满足 \(a[j] \leq a[i] - d\),这就说明只会从值较大的向值较小的跳跃,不妨用以下数据结构维护排列 \(a\) 的每一个节点:

struct node {
    int x;// 节点值
    int idx;// 节点下标
} a[N];

那么可以考虑对排列 \(a\) 的下标按照元素值从大到小进行排序(下文提及的排列 \(a\) 若未专门标注,则默认表示的是排序后的排列 \(a\)),随后使用快慢指针进行维护,快指针维护下标 \(i\),慢指针维护下标 \(j\)。

对于条件 \(1 \leq |i-j| \leq r\),可以使用最大值线段树进行维护。具体地,创建一颗大小为 \(n + 1\) 的最大值线段树(这里的大小指的是维护共有 \(n + 1\) 个叶子节点,线段树的节点个数需要开 \(4 \times (n + 1)\) 个节点),将线段树的每个叶子节点的值初始化为 \(0\),第 \(x\) 个叶子结点的值代表以排列 \(a\) 的第 \(x\) 个节点为跳跃终点的最大跳跃次数。线段树的第 \(x\) 个叶子节点,对应的是初始未排序的排列 \(a\) 的元素下标。

此外,再创建一个大小为 \(n+1\) 的数组 \(mx\),其中 \(mx[j]\) 代表跳跃终点为排列 \(a\) 第 \(j\) 个元素的最大跳跃次数,初始化为 \(0\)。

当慢指针维护到下标 \(j\) 时,将 \(mx[j]\) 更新到最值线段树的第 \(a[j].idx\) 个叶子节点。当慢指针无法再移动时,求出在最值线段树的区间 \([max(1, a[i].idx-r), min(n,a[i].idx+r)]\) 上的最大值,加上 \(1\) (因为节点发生跳跃,所以节点多了一个)以后加在 \(mx[i]\) 上。

因为跳跃不计入起点,因此最后的统计结果,需要再减去 \(1\)。

参考代码

#include<bits/stdc++.h>
#define lc(x) ((x)<<1)
#define rc(x) ((x)<<1|1)
using namespace std;

constexpr int N = 5e5 + 7;
int ans, n, d, r;
int mx[N];
struct node {
    int x;// 节点值
    int idx;// 节点下标
} a[N];
struct treeNode {
    int l, r;// 区间[l, r]
    int max;// 最大值
    int lazy;// 懒修改值
} tr[N << 2];

void pushUp(int p) {
    tr[p].max = max(tr[lc(p)].max, tr[rc(p)].max);
}

void pushDown(int p) {
    if (tr[p].lazy) {
        int l = lc(p), r = rc(p);
        
        tr[l].max = max(tr[l].max, tr[p].lazy);
        tr[l].lazy = max(tr[l].lazy, tr[p].lazy);
        
        tr[r].max = max(tr[r].max, tr[p].lazy);
        tr[r].lazy = max(tr[r].lazy, tr[p].lazy);
        
        tr[p].lazy = 0;
    }
}

void build(int p/*线段树节点编号*/, int l/*该节点管理的区间左端点*/, int r/*该节点管理的区间右端点*/) {
    tr[p] = {l, r, 0, 0};// 节点 p 维护区间 [l, r] 的信息
    if (l == r) return ;// 若已经是叶子节点,无需再次递归到子节点,可以直接返回
    int m = l + r >> 1;// 二分线段 [l, r],将线段划分为左子树 [l, m] 和右子树 [m + 1, r]
    build(lc(p), l, m);// 建立左子树
    build(rc(p), m + 1, r);// 建立右子树
    pushUp(p);// 根据左右子树信息,更新当前节点信息
}

void update(int p, int l, int r, int k) {
    if (l <= tr[p].l && tr[p].r <= r) {// 节点 p 的管辖区间 [tr[p].l, tr[p].r] 位于 [l, r] 之间
        tr[p].lazy = max(tr[p].lazy, k);// 更新懒标记信息,记录子节点需要更新的数据
        tr[p].max = max(tr[p].max, k);// 更新当前节点的最大值
        return ;
    }
    pushDown(p);// 将懒标记信息传给左右子节点
    int m = tr[p].l + tr[p].r >> 1;// 二分区间 [tr[p].l, tr[p].r],划分为 [tr[p].l, m] 和 [m + 1, tr[p],r]
    if (l <= m) update(lc(p), l, r, k);// 如果左子树维护的区间 [tr[lc(p).l, tr[lc(p)].r] 和 [l, r] 有交集则递归左子树
    if (m < r) update(rc(p), l, r, k);// 如果右子树维护的区间 [tr[rc(p).l, tr[rc(p)].r] 和 [l, r] 有交集则递归右子树
    pushUp(p);// 左右子树更新完,需要更新当前节点信息
}

int query(int p, int l, int r) {
    if (l <= tr[p].l && tr[p].r <= r) return tr[p].max;// 节点 p 的管辖区间 [tr[p].l, tr[p].r] 位于 [l, r] 之间
    int ans = 0;
    pushDown(p);// 将懒标记信息传给左右子节点
    int m = tr[p].l + tr[p].r >> 1;// 二分区间 [tr[p].l, tr[p].r],划分为 [tr[p].l, m] 和 [m + 1, tr[p],r]
    if (l <= m) ans = query(lc(p), l, r);// 如果左子树维护的区间 [tr[lc(p).l, tr[lc(p)].r] 和 [l, r] 有交集则递归左子树
    if (m < r) ans = max(ans, query(rc(p), l, r));// 如果右子树维护的区间 [tr[rc(p).l, tr[rc(p)].r] 和 [l, r] 有交集则递归右子树
    return ans;
}

int main() {
    ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
    cin >> n >> d >> r;
    for (int i = 1; i <= n; ++ i) {
        cin >> a[i].x;
        a[i].idx = i;
    }
    // 先按值从大到小排,再按下班从大到小排
    sort(a + 1, a + 1 + n, [&](node &x, node &y) {
        return x.x != y.x ? x.x > y.x : x.idx > y.idx;
    });
    
    // 建立最值线段树
    build(1, 1, n);
    
    for (int i = 1, j = 1; i <= n; ++ i) {
        while (a[i].x <= a[j].x - d) {
            update(1, a[j].idx, a[j].idx, mx[j]);
            ++ j;
        }
        mx[i] = 1 + query(1, max(a[i].idx - r, 1), min(n, a[i].idx + r));
        ans = max(ans, mx[i]);
    }
    
	cout << ans - 1 << '\n';
    return 0;
}

posted on 2026-07-02 19:58  RomanLin  阅读(1)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3