P1438 无聊的数列 线段树 差分线段树维护

解题思路与代码注释

题目理解

这道题目要求我们维护一个数列,支持两种操作:

  1. 区间加等差数列:在区间[l,r]加上一个首项为K,公差为D的等差数列

  2. 单点查询:查询数列中第p个数的值

解题方法

使用差分数组+线段树的方法来高效处理区间修改和单点查询:

  1. 差分数组

    • 将原数组a转换为差分数组d,其中d[1]=a[1]d[i]=a[i]-a[i-1](i>1)

    • 这样区间加等差数列可以转换为差分数组上的几个单点/区间修改

  2. 线段树

    • 维护差分数组的区间和

    • 支持区间加法和区间查询

代码详细注释

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

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

// 线段树节点结构体
struct node{
    ll sum;   // 区间和
    ll lazy;  // 懒标记
};
node t[N << 2];  // 线段树数组
ll n, m, a[N], d[N];  // n-数列长度,m-操作次数,a-原数组,d-差分数组

// 更新父节点的值
void pushup(int rt) {
    t[rt].sum = t[lc].sum + t[rc].sum;  // 父节点和等于左右子节点和相加
}

// 下放懒标记操作
void down(int rt, int l, int r, ll val) {
    t[rt].sum += (r - l + 1) * val;  // 更新区间和
    t[rt].lazy += val;               // 累加懒标记
}

// 下推懒标记到子节点
void pushdown(int rt, int l, int r) {
    if(t[rt].lazy == 0) return;  // 无懒标记则返回
    int mid = (l + r) >> 1; 
    down(lson, t[rt].lazy);  // 下放到左子树
    down(rson, t[rt].lazy);  // 下放到右子树
    t[rt].lazy = 0;          // 清空当前节点的懒标记
}

// 构建线段树
void build(int rt, int l, int r) {
    t[rt].lazy = 0;  // 初始化懒标记
    if(l == r) {     // 叶子节点
        t[rt].sum = d[l];  // 存储差分数组的值
        return;
    }
    int mid = (l + r) >> 1;
    build(lson);  // 构建左子树
    build(rson);  // 构建右子树
    pushup(rt);   // 更新父节点信息
}

// 区间修改函数
void change(int rt, int l, int r, int x, int y, ll val) {
    if(r < x || y < l) return;  // 完全不在区间内
    if(x <= l && r <= y) {      // 完全包含在区间内
        t[rt].sum += (r - l + 1) * val;  // 更新区间和
        t[rt].lazy += val;               // 设置懒标记
        return;
    }
    pushdown(rt, l, r);  // 下放懒标记
    int mid = (l + r) >> 1;
    change(lson, x, y, val);  // 修改左子树
    change(rson, x, y, val);  // 修改右子树
    pushup(rt);               // 更新父节点信息
}

// 区间查询函数
ll query(int rt, int l, int r, int x, int y) {
    if(r < x || y < l) return 0;  // 区间无交集返回0
    if(x <= l && r <= y) return t[rt].sum;  // 完全包含直接返回区间和
    pushdown(rt, l, r);  // 下放懒标记
    int mid = (l + r) >> 1;
    // 返回左右子树查询结果的和
    return query(lson, x, y) + query(rson, x, y);
}

int main() {
    cin >> n >> m;
    // 读取原数组并计算差分数组
    for(int i = 1; i <= n; i++) {
        scanf("%lld", &a[i]);
        d[i] = a[i] - a[i - 1];  // 计算差分数组
    }
    build(1, 1, n);  // 构建线段树

    while(m--) {
        int op, x, y, k, d_val;  // d_val避免与差分数组d重名
        scanf("%d", &op);
        if(op == 1) {  // 区间加等差数列操作
            scanf("%d%d%d%d", &x, &y, &k, &d_val);
            // 差分数组修改:
            change(1, 1, n, x, x, k);  // 首项:d[x] += k
            if(x + 1 <= y) 
                change(1, 1, n, x + 1, y, d_val);  // 公差:d[x+1..y] += d
            if(y + 1 <= n) 
                change(1, 1, n, y + 1, y + 1, -((y - x) * d_val + k));  // 结束修正
        } else {  // 单点查询操作
            scanf("%d", &x);
            // 查询前缀和:a[x] = d[1] + d[2] + ... + d[x]
            printf("%lld\n", query(1, 1, n, 1, x));
        }
    }
    return 0;
}

关键点解释

  1. 差分数组转换

    • 将区间加等差数列转换为差分数组上的三个操作

    • 首项处理:d[l] += K

    • 公差处理:d[l+1..r] += D

    • 结束修正:d[r+1] -= (K + (r-l)*D)

  2. 线段树功能

    • 维护差分数组的区间和

    • 支持高效的区间加法和区间查询

  3. 单点查询

    • 原数组的值a[p]等于差分数组前p项的和

    • 通过query(1,1,n,1,p)查询

复杂度分析

  • 时间复杂度

    • 每次区间修改:O(log n)

    • 每次单点查询:O(log n)

    • 总复杂度:O(m log n)

  • 空间复杂度:O(n)

这个实现能够高效处理题目给出的最大数据规模,保证在时间限制内完成所有操作。

posted @ 2025-05-21 14:31  CRt0729  阅读(51)  评论(0)    收藏  举报