P1886 滑动窗口 /【模板】单调队列

解题思路

这道题目是经典的滑动窗口最值问题,要求在一个长度为n的序列中,对于每个长度为k的滑动窗口,求出窗口中的最小值和最大值。

方法选择

题目提供的代码使用了线段树解法,这是可行的但并非最优解。线段树的主要特点包括:

  1. 预处理时间O(n)

  2. 每个查询时间O(logn)

  3. 可以处理动态更新的情况

但对于滑动窗口这种特殊问题,更优的解法是使用单调队列:

  1. 单调队列解法

    • 预处理时间O(n)

    • 每个查询时间O(1)(均摊)

    • 空间复杂度O(n)

    • 特别适合滑动窗口这类固定窗口大小的问题

线段树解法的优缺点

优点

  • 代码结构清晰

  • 可以处理更一般的区间查询问题

  • 支持动态更新

缺点

  • 对于滑动窗口这种特殊问题,时间复杂度不如单调队列优秀

  • 空间消耗较大(需要4倍原始数组空间)

#include<bits/stdc++.h>
#define ll long long
#define lc rt << 1      // 左子节点索引
#define rc rt << 1 | 1  // 右子节点索引
#define lson lc,l,mid   // 左子树参数
#define rson rc,mid + 1,r // 右子树参数
using namespace std;

const int N = 1e6 + 10, inf = 0x3f3f3f3f;

// 线段树节点结构体
struct node{
    ll sum;    // 区间和(本问题中未使用)
    ll minn;   // 区间最小值
    ll maxx;   // 区间最大值
};

node t[N << 2];  // 线段树数组
ll n, m;         // n-序列长度,m-窗口大小
ll a[N];         // 原始序列

// 更新父节点信息
void pushup(int rt)
{
    t[rt].sum = t[lc].sum + t[rc].sum;  // 区间和(未使用)
    t[rt].minn = min(t[lc].minn, t[rc].minn); // 区间最小值
    t[rt].maxx = max(t[lc].maxx, t[rc].maxx); // 区间最大值
}

// 构建线段树
void build(int rt, int l, int r)
{
    if(l == r){  // 叶子节点
        t[rt].sum = a[l];
        t[rt].minn = t[rt].maxx = a[l];  // 单个元素的min和max就是它本身
        return;
    }
    int mid = (l + r) >> 1;  // 计算中点
    build(lson);  // 构建左子树
    build(rson);  // 构建右子树
    pushup(rt);   // 更新当前节点信息
}

// 查询区间最小值
ll query(int rt, int l, int r, int x, int y)
{
    if(r < x || y < l) return inf;  // 区间无交集,返回极大值
    if(x <= l && r <= y) return t[rt].minn;  // 完全包含,直接返回最小值
    int mid = (l + r) / 2;
    // 返回左右子树查询结果的较小值
    return min(query(lson, x, y), query(rson, x, y));
}

// 查询区间最大值
ll query2(int rt, int l, int r, int x, int y)
{
    if(r < x || y < l) return -inf;  // 区间无交集,返回极小值
    if(x <= l && r <= y) return t[rt].maxx;  // 完全包含,直接返回最大值
    int mid = (l + r) / 2;
    // 返回左右子树查询结果的较大值
    return max(query2(lson, x, y), query2(rson, x, y));
}

int main()
{
    cin >> n >> m;  // 读取序列长度和窗口大小
    for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);  // 读取序列
    
    build(1, 1, n);  // 构建线段树
    
    // 输出每个窗口的最小值
    for(int i = m; i <= n; i++)
    {
        int x = i - m + 1, y = i;  // 计算窗口位置[x,y]
        printf("%lld ", query(1, 1, n, x, y));  // 查询并输出最小值
    }
    cout << endl;
    
    // 输出每个窗口的最大值
    for(int i = m; i <= n; i++)
    {
        int x = i - m + 1, y = i;  // 计算窗口位置[x,y]
        printf("%lld ", query2(1, 1, n, x, y));  // 查询并输出最大值
    }
    
    return 0;
}

 

posted @ 2025-05-21 09:30  CRt0729  阅读(18)  评论(0)    收藏  举报