P1438 无聊的数列 线段树 差分线段树维护
解题思路与代码注释
题目理解
这道题目要求我们维护一个数列,支持两种操作:
-
区间加等差数列:在区间
[l,r]加上一个首项为K,公差为D的等差数列 -
单点查询:查询数列中第p个数的值
解题方法
使用差分数组+线段树的方法来高效处理区间修改和单点查询:
-
差分数组:
-
将原数组
a转换为差分数组d,其中d[1]=a[1],d[i]=a[i]-a[i-1](i>1) -
这样区间加等差数列可以转换为差分数组上的几个单点/区间修改
-
-
线段树:
-
维护差分数组的区间和
-
支持区间加法和区间查询
-
代码详细注释
#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; }
关键点解释
-
差分数组转换:
-
将区间加等差数列转换为差分数组上的三个操作
-
首项处理:
d[l] += K -
公差处理:
d[l+1..r] += D -
结束修正:
d[r+1] -= (K + (r-l)*D)
-
-
线段树功能:
-
维护差分数组的区间和
-
支持高效的区间加法和区间查询
-
-
单点查询:
-
原数组的值
a[p]等于差分数组前p项的和 -
通过
query(1,1,n,1,p)查询
-
复杂度分析
-
时间复杂度:
-
每次区间修改:O(log n)
-
每次单点查询:O(log n)
-
总复杂度:O(m log n)
-
-
空间复杂度:O(n)
这个实现能够高效处理题目给出的最大数据规模,保证在时间限制内完成所有操作。

浙公网安备 33010602011771号