题解:AtCoder AT_awc0026_e Just the Right Temperature Difference

【题目来源】

AtCoder:E - Just the Right Temperature Difference

【题目描述】

Takahashi works at a weather observatory and is recording the daily maximum temperatures over \(N\) days for a certain region. The maximum temperature on day \(i\) (\(1 \leq i \leq N\)) is \(A_i\) ℃.
高桥在气象观测站工作,正在记录某个地区连续 \(N\) 天的日最高气温。第 \(i\) 天(\(1 \leq i \leq N\))的最高气温为 \(A_i\) ℃。

Takahashi wants to select a contiguous interval of days and examine the temperature fluctuation range. For an interval from day \(l\) to day \(r\) (\(1 \leq l \leq r \leq N\)), the "fluctuation range" of that interval is defined as:
高桥想选择一个连续的天数区间,并检查其温度波动范围。对于从第 \(l\) 天到第 \(r\) 天(\(1 \leq l \leq r \leq N\))的区间,该区间的"波动范围"定义为:

\(\max(A_l, A_{l+1}, \dots, A_r) - \min(A_l, A_{l+1}, \dots, A_r)\)

In particular, when \(l = r\) (an interval consisting of only one day), the fluctuation range is \(0\).
特别地,当 \(l = r\)(只包含一天的区间)时,波动范围为 \(0\)

Given a positive integer \(N\), a non-negative integer \(K\), and a sequence of maximum temperatures over \(N\) days \(A_1, A_2, \dots, A_N\), find the number of intervals whose fluctuation range is exactly \(K\). That is, find the number of integer pairs \((l, r)\) (\(1 \leq l \leq r \leq N\)) satisfying:
给定一个正整数 \(N\)、一个非负整数 \(K\),以及 \(N\) 天的最高气温序列 \(A_1, A_2, \dots, A_N\),求波动范围恰好为 \(K\) 的区间数量。也就是说,求满足以下条件的整数对 \((l, r)\)\(1 \leq l \leq r \leq N\))的数量:

\(\max(A_l, A_{l+1}, \dots, A_r) - \min(A_l, A_{l+1}, \dots, A_r) = K\)

【输入】

\(N\) \(K\)
\(A_1\) \(A_2\) \(\dots\) \(A_N\)

The first line contains a positive integer \(N\) representing the number of days and a non-negative integer \(K\) representing the specified fluctuation range, separated by a space. The second line contains integers \(A_1, A_2, \dots, A_N\) representing the maximum temperature on each day, separated by spaces.

【输出】

Output the number of pairs \((l, r)\) whose fluctuation range is exactly \(K\), on a single line.

【输入样例】

5 3
1 4 2 5 3

【输出样例】

6

【核心思想】

  1. 问题分析:给定长度为 \(N\) 的数组 \(A_1, A_2, \dots, A_N\),需要统计满足 \(\max(A_l, \dots, A_r) - \min(A_l, \dots, A_r) = K\) 的区间 \([l, r]\) 数量。这是一个双指针 + 单调队列问题,关键在于高效维护区间内的最大值和最小值。

  2. 算法选择

    • 双指针(Two Pointers):维护一个滑动窗口 \([l, r]\),通过移动左右指针来枚举所有可能的区间
    • 单调队列(Monotonic Deque):分别用两个单调队列维护当前窗口内的最小值和最大值,实现 \(O(1)\) 查询区间极值
    • 容斥原理:差值恰好等于 \(K\) 的区间数 = 差值不超过 \(K\) 的区间数 - 差值不超过 \(K-1\) 的区间数
  3. 关键步骤

    • 初始化:读取 \(N\)(天数)、\(K\)(目标波动范围)、\(A[1..N]\)(温度序列)
    • calc(k)函数:计算差值不超过 \(k\) 的区间数量
      • 使用双指针 \(l\)\(r\) 维护当前区间
      • dqmin:单调递增队列,维护区间最小值(队首为最小值)
      • dqmax:单调递减队列,维护区间最大值(队首为最大值)
    • 扩展右指针
      • \(A[r]\) 加入 dqmin:从队尾弹出所有 \(\geq A[r]\) 的元素,然后入队
      • \(A[r]\) 加入 dqmax:从队尾弹出所有 \(\leq A[r]\) 的元素,然后入队
    • 收缩左指针
      • dqmax.front().v - dqmin.front().v > k 时,说明当前区间不满足条件
      • 移动左指针 \(l\):如果队首元素的下标等于 \(l\),则从队列中弹出
      • 重复直到区间满足条件
    • 统计区间数:以 \(r-1\) 为右端点的满足条件的区间数量为 \(r - l + 1\),累加到答案
    • 容斥计算ans = calc(K) - calc(K-1)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N)\),每个元素最多入队和出队各一次
    • 空间复杂度:\(O(N)\),两个单调队列的空间
  5. 双指针与单调队列的核心思想

    • 滑动窗口:双指针维护一个动态区间,右指针扩展,左指针收缩,保证窗口内始终满足条件
    • 单调队列维护极值:通过维护单调性,使得队首始终是当前窗口的最值,实现 \(O(1)\) 查询
    • 过期元素处理:当左指针移动时,检查队首元素是否已过期(下标 < l),及时弹出
    • 容斥转化:将"恰好等于 \(K\)"转化为"不超过 \(K\)"的差值,简化问题处理
    • 适用于区间统计、滑动窗口最值类问题

【解题思路】

【算法标签】

双指针

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 200005;
int n, k;
int a[N], ans;
struct Node
{
    int v, idx;  // v: 数组值, idx: 在数组中的下标
};

// 计算最大值与最小值之差不超过k的区间数量
int calc(int k)
{
    int res = 0;           // 结果计数器
    int l = 1, r = 1;      // 双指针,表示当前区间[l, r]
    deque<Node> dqmin, dqmax;  // 单调队列,分别维护最小值和最大值

    while (l <= n && r <= n)  // 双指针遍历数组
    {
        // 将a[r]加入最小值队列(维护单调递增)
        while (!dqmin.empty() && dqmin.back().v >= a[r])
        {
            dqmin.pop_back();  // 移除队尾比a[r]大的元素
        }
        dqmin.push_back({a[r], r});  // 将a[r]加入队列

        // 将a[r]加入最大值队列(维护单调递减)
        while (!dqmax.empty() && dqmax.back().v <= a[r])
        {
            dqmax.pop_back();  // 移除队尾比a[r]小的元素
        }
        dqmax.push_back({a[r], r});  // 将a[r]加入队列

        // 调整左指针l,使当前区间[l, r]的最大值-最小值<=k
        while (!dqmin.empty() && !dqmax.empty() && dqmax.front().v - dqmin.front().v > k)
        {
            // 如果队首元素是左指针l指向的元素,则从队列中移除
            if (dqmax.front().idx == l)
            {
                dqmax.pop_front();  // 从最大值队列中移除过期的元素
            }
            if (dqmin.front().idx == l)
            {
                dqmin.pop_front();  // 从最小值队列中移除过期的元素
            }
            l++;  // 左指针右移,缩小区间
        }
        r++;  // 右指针右移,扩展区间

        // 统计以r-1为右端点的满足条件的区间数量
        // 区间[l, r-1]满足条件,所以有(r-1)-l+1 = r-l+1个区间
        res += r - l + 1;
    }
    return res;  // 返回差值不超过k的区间总数
}

signed main()
{
    cin >> n >> k;  // 读入数组长度n和差值限制k
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];  // 读入数组元素
    }

    // 差值恰好等于k的区间数 = 差值不超过k的区间数 - 差值不超过k-1的区间数
    cout << calc(k) - calc(k - 1) << endl;
    return 0;
}

【运行结果】

5 3
1 4 2 5 3
6
posted @ 2026-06-27 14:22  团爸讲算法  阅读(5)  评论(0)    收藏  举报