当前位置:首页 > 洛谷 > 洛谷P1438题解:基于线段树的等差数列

洛谷P1438题解:基于线段树的等差数列

5天前

洛谷P1438题解:基于线段树的等差数列 洛谷题解 线段树 等差数列 递归 C++ 第1张

一、题目解读

洛谷P1438题要求处理数列的区间更新与单点查询操作,其中更新方式为给定区间内元素按等差数列递增。传统暴力修改会因多次区间遍历导致超时,需设计高效数据结构——线段树,结合等差数列性质实现O(logN)复杂度解决方案。

二、解题思路

核心思想为利用线段维护区间和、首项与公差,通过“懒惰标记”延迟更新,避免子树重复计算。每次区间修改时,仅传递增量k(首项增量)与d(公差增量)至对应节点,查询时通过公式还原原数列值。关键在于推导节点合并与标记下传时的数学逻辑,确保区间和正确更新。

三、解题步骤

1. 构建线段树:自顶向下递归,叶子节点存储单点值,非叶节点维护子区间和。

2. 懒惰标记下传(pushDown):

○ 当节点存在未传递的k、d时,计算左子树更新后的sum、k、d,并调整右子树k(考虑左区间长度影响)。

○ 清空当前节点标记,避免重复更新。

3. 区间更新(rangeAdd):

○ 递归至目标区间覆盖的节点,下传父节点标记后,更新当前节点k、d及sum。

4. 单点查询(query):递归至目标点,沿途累加经过节点的sum。

四、代码与注释

#include <iostream>
#include <vector>
using namespace std;

// 线段树节点结构
struct Node {
    long long sum;  // 区间和
    long long k;    // 首项
    long long d;    // 公差
    int l, r;       // 区间范围
    Node *left, *right;
    
    Node(int l, int r) : l(l), r(r), k(0), d(0), left(nullptr), right(nullptr) {} // 构造函数初始化
};

class SegmentTree {
public:
    SegmentTree(const vector<int>& nums) {
        root = build(nums, 1, nums.size()); // 构建线段树
    }
    
    // 区间更新等差数列
    void rangeAdd(int l, int r, long long k, long long d) {
        update(root, l, r, k, d); // 递归更新
    }
    
    // 单点查询
    long long query(int p) {
        return query(root, p); // 递归查询
    }

private:
    Node* root;
    
    // 构建线段树(递归)
    Node* build(const vector<int>& nums, int l, int r) {
        Node* node = new Node(l, r); // 创建节点
        if (l == r) { // 叶子节点赋值
            node->sum = nums[l-1];
            return node;
        }
        int mid = (l + r) / 2; // 中分区间
        node->left = build(nums, l, mid); // 左子树
        node->right = build(nums, mid+1, r); // 右子树
        node->sum = node->left->sum + node->right->sum; // 合并区间和
        return node;
    }
    
    // 标记下传(核心优化)
    void pushDown(Node* node) {
        if (node->k!= 0 || node->d!= 0) { // 存在未传递标记
            if (node->left) { // 左子树更新
                int len = node->left->r - node->left->l + 1; // 区间长度
                node->left->k += node->k; // 首项增量累加
                node->left->d += node->d; // 公差增量累加
                node->left->sum += node->k * len + node->d * len * (len - 1) / 2; // 更新区间和(等差数列求和公式)
                
                // 调整右子树标记(考虑左区间对右区间首项的影响)
                node->right->k += node->k + node->d * len;
                node->right->d += node->d;
                int r_len = node->right->r - node->right->l + 1;
                node->right->sum += (node->k + node->d * len) * r_len + node->d * r_len * (r_len - 1) / 2;
            }
            node->k = 0; // 清空当前节点标记
            node->d = 0;
        }
    }
    
    // 区间更新递归函数
    void update(Node* node, int l, int r, long long k, long long d) {
        if (node->l >= l && node->r <= r) { // 目标区间完全覆盖当前节点
            node->k += k; // 累加首项增量
            node->d += d; // 累加公差增量
            int len = node->r - node->l + 1; // 当前区间长度
            node->sum += k * len + d * len * (len - 1) / 2; // 更新区间和
        } else { // 区间部分覆盖,递归处理
            pushDown(node); // 下传父节点标记
            int mid = (node->l + node->r) / 2;
            if (l <= mid) update(node->left, l, r, k, d); // 左子树更新
            if (r > mid) update(node->right, l, r, k, d); // 右子树更新
            node->sum = node->left->sum + node->right->sum; // 合并子树和
        }
    }
    
    // 单点查询递归函数
    long long query(Node* node, int p) {
        if (node->l == node->r) return node->sum; // 到达目标点直接返回
        pushDown(node); // 下传标记
        int mid = (node->l + node->r) / 2;
        return p <= mid? query(node->left, p) : query(node->right, p); // 递归查询
    }
};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n, m;
    cin >> n >> m;
    vector<int> a(n);
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
    }
    
    SegmentTree st(a);
    
    while (m--) {
        int opt;
        cin >> opt;
        if (opt == 1) {
            int l, r;
            long long k, d;
            cin >> l >> r >> k >> d;
            st.rangeAdd(l, r, k, d);
        } else {
            int p;
            cin >> p;
            cout << st.query(p) << '\n';
        }
    }
    
    return 0;
}

五、总结

本解法通过线段树结合等差数列特性,巧妙利用“懒惰标记”减少冗余计算,实现高效区间更新。关键突破在于标记下传时,对子树k、d的传递公式推导需严谨考虑区间长度影响。此外,代码结构清晰,递归逻辑易扩展至其他区间修改场景。掌握此类优化技巧可显著提升算法竞赛中动态区间问题的解题效率。


原创内容 转载请注明出处

分享给朋友:

相关文章

力扣70题:告别暴力递归!从零实现记忆化搜索解法

力扣70题:告别暴力递归!从零实现记忆化搜索解法

题意解析:想象你站在楼梯底部,面前有n级台阶。每次你可以选择跨1级或2级台阶,最终到达顶端的路径有多少种不同的走法?这个问题本质上是在探索分叉决策的叠加效果——当我们把每个台阶处的选择看作二叉树的分支...

【深度优先搜索实战】力扣547题:省份数量问题的图论解法

【深度优先搜索实战】力扣547题:省份数量问题的图论解法

题目解读‌我们面对的是一个典型的图论问题:给定一个城市的连接矩阵,需要计算其中相互连通的城市群(省份)数量。这个问题可以抽象为无向图中的连通分量计算,每个城市代表图中的一个节点,城市之间的连接关系代表...

力扣1137题:动态规划解泰波那契数 高效求解第N项的秘密

力扣1137题:动态规划解泰波那契数 高效求解第N项的秘密

一:重新解读题目泰波那契数列是一个充满数学趣味的递推序列:从第3项开始,每个数均为前三个数的和(即Tₙ₊₃ = Tₙ + Tₙ₊₁ + Tₙ₊₂)。当给定整数n时,需要高效计算出第n项的值。面对此类递...

力扣第71题:用栈轻松解决Unix路径简化问题

力扣第71题:用栈轻松解决Unix路径简化问题

题目解读:在Unix风格的文件系统中,我们经常需要处理各种复杂的路径表示。给定一个绝对路径字符串,我们需要将其转换为最简化的规范路径。规范路径要求:路径始终以斜杠'/'开头;两个目录名...

IOI 1994 洛谷1216:如何用O(1)空间解决数字三角形问题?附代码实现

IOI 1994 洛谷1216:如何用O(1)空间解决数字三角形问题?附代码实现

题目重解:数字三角形是一个经典的动态规划问题,给定一个由数字组成的三角形结构,从顶部出发,每次可以移动到下方相邻的数字,最终到达底部。我们需要找到一条路径,使得路径上经过的数字总和最大。这个问题可以很...

力扣3112题解法:带时间限制的最短路径问题解析(C++代码)

力扣3112题解法:带时间限制的最短路径问题解析(C++代码)

一、题目解读力扣3112题要求解决带时间限制的最短路径问题:给定一个有向图,节点具有消失时间,需计算从起点到各节点的最短路径,且路径总时间不能超过节点的消失时间。题目难点在于需在传统最短路径算法(如D...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。