线段树
线段树
题目链接:https://www.luogu.com.cn/problem/P3372
题目:
题目描述 如题,已知一个数列,你需要进行下面两种操作:
将某区间每一个数加上 kk。
求出某区间每一个数的和。
输入格式
第一行包含两个整数 n, mn,m,分别表示该数列数字的个数和操作的总个数。
第二行包含 nn 个用空格分隔的整数,其中第 ii 个数字表示数列第 ii 项的初始值。
接下来 mm 行每行包含 33 或 44 个整数,表示一个操作,具体如下:
1 x y k:将区间 [x, y][x,y] 内每个数加上 kk。
2 x y:输出区间 [x, y][x,y] 内每个数的和。
输出格式
输出包含若干行整数,即为所有操作 2 的结果。
输入输出样例
5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4
输出
11
8
20
参考博客:https://blog.csdn.net/cowgoodsheep/article/details/123358880
参考博客:https://zhuanlan.zhihu.com/p/106118909
解题思路:
如果每次都仅仅查询每段的和的话,我们可以累加段的和,然后用大段和减小段得到中间一段的和。
用raw_data[i]表示原数据的第i个
用data[i]表示data[0]-data[i]的和,可知段s-e的和为data[e]-data[s-1]。
但是本题中还需要修改某一段,如果暴力修改每一个数据太慢了,期望能够有一个能批量修改的方法,比如一个节点表示了s-e的这段数据的和,那么假如要给这一段再加d,那么我们只需要修改这个节点即可,但是不可能给每一段都搞一个这样的节点,所以我们可以采用二叉树,用二分的思想,这样任何一段都能被二叉树中的点分担。
可这我们拿到初始数据后需要建立二叉树,build
更新二叉树需要update
查询二叉树需要query
build:
数组二叉树如果把1当作根,那么每个节点的左右孩子比较好找,root2和root2+1,所以我们从1开始建树。
假设有n个数据,1-n,为了查询更快,我们需要建立一颗平衡树。
设根为root,可知root这个位置要存1-n的数据之和,为了平衡树,所以每次左右子树各给一半,左子树的位置是root2,这位置存1-n/2的和,右子树的位置是root2+1,存n/2+1-n的数据之和。不断递归直到只剩一个数据不用再分左右子树了,就把数据直接给这个节点。
update:
假设要给l到r加上d,那么我们从根节点开始分配任务,把任务丢给左右孩子,左右孩子更新好后,更新当前root的值。
如果一个孩子管理的一段全部属于要更改的那一大段,那么就只在在这个节点这里打个懒标记mark[root]+=d,并且把当前节点的值直接更新一下即可tree[root]+=d*(cr-cl+1),这里的cl和cr表示当前节点管理的最左边和最右边的序号,这样就不用每次都遍历到底。如果需要访问孩子,那么把当前节点的懒标记mark[root]给分发给子节点即可(push_down)。
query:
假设要查询l到r的和,那么还是从根节点分发任务,让左右孩子解决,然后返回左右孩子结果的和。
如果有孩子管理的一段属于l到r,那么直接返回当前节点的值即可。
如果当前节点有懒标记,那么同update一样先push_down一下,把当前节点的懒标记分发给孩子即可。
push_down:
分发root的懒标记时要注意,一个节点的懒标记动的时候一定要把动的量及时更新到其节点上,因为懒标记可以叠加,如果不及时更新,到时候就分不清懒标记中哪部分已经更新了,哪部分没更新了。
不及时更新的错误举例:
比如一个节点root1,第一次让root1加1,然后询问root1这个节点的结果,这下你加了一下mark这个懒标记,但是由于可能要push_down给孩子节点,你不能清除该懒标记
下次又更新一次root1,让root1加2,再询问root1,这次你怎么知道mark这个懒标记1那部分已经更新过了,而这次的2没有呢?混到一起了。
所以每次动一个节点的mark懒标记时,都要立即更新它的值
// SegmentTree.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <stdio.h>
#include <string.h>
#include <queue>
using namespace std;
using ll = long long;
const int MAXN = 1e5 + 5;
ll tree[MAXN*3], mark[MAXN*3], n, m, raw_data[MAXN];
void build(int root, int cl, int cr)
{
// 以root为根节点,处理原始数据的cl到cr这部分的数据
// 如果cl==cr说明就一个数,那么就直接放在root这里就好
// 否则以root为根节点,左右孩子各处理一半
if (cl == cr)
{
tree[root] = raw_data[cl];
return;
}
int mid = (cl + cr) /2;
int left_child = root * 2, right_child = root * 2 + 1;
build(left_child, cl, mid);
build(right_child, mid + 1, cr);
// 左右孩子的树建立好了之后,更新一下当前root的结果,这里是求和,也可以是其他运算
tree[root] = tree[left_child] + tree[right_child];
}
void push_down(int root, int cl, int cr)
{
// push_down的作用就是把当前的懒标记,更新给孩子,因为一定是有人调用了孩子,这个懒标记给了孩子之后记得把标记去除
int mid = (cl + cr) / 2;
int left_child = root * 2, right_child = root * 2 + 1;
mark[left_child] += mark[root];
mark[right_child] += mark[root];
// 懒标记有可能会累积,如果不及时把数值加到tree上,到时候就不知道有多少标记已经被加过了,有多少没被加过,所以这里及时把这次的数值累加上来
// 不及时更新的错误举例:
// 比如一个节点root1,第一次让root1加1,然后询问root1这个节点的结果,这下你加了一下mark这个懒标记,但是由于可能要push_down给孩子节点,你不能清除该懒标记
// 下次又更新一次root1,让root1加2,再询问root1,这次你怎么知道mark这个懒标记1那部分已经更新过了,而这次的2没有呢?混到一起了。
// 所以每次动一个节点的mark懒标记时,都要立即更新它的值
tree[left_child] += (mark[root] * (mid - cl + 1));
tree[right_child] += (mark[root] * (cr - mid));
mark[root] = 0;
}
void update(int root, int cl, int cr, int l, int r, int d)
{
// 更新区域与当前区域没有交集
if (r < cl || cr < l)return;
// 更新区域包含当前区域
if (l <= cl && cr <= r)
{
mark[root] += d;
tree[root] += d*(cr - cl + 1);
if (cl == cr)mark[root] = 0;
return;
}
// 更新区域包含当前区域的一部分
int mid = (cl + cr) / 2;
int left_child = root * 2, right_child = root * 2 + 1;
push_down(root, cl, cr);
update(root * 2, cl, mid, l, r, d);
update(root * 2+1, mid+1, cr, l, r, d);
tree[root] = tree[left_child] + tree[right_child];
}
ll query(int root, int cl, int cr, int l, int r)
{
// 更新区域与当前区域没有交集
if (r < cl || cr < l)return 0;
// 更新区域包含当前区域
if (l <= cl && cr <= r)return tree[root];
int mid = (cl + cr) / 2;
int left_child = root * 2, right_child = root * 2 + 1;
push_down(root, cl, cr);
ll ans = query(left_child, cl, mid, l, r)+query(right_child, mid+1, cr, l, r);
return ans;
}
void PrintTree()
{
queue<int> q;
q.push(1);
printf("tree start\n");
int max_no = 9;
while (q.size())
{
int layer_size = q.size();
for (int i = 0; i != layer_size; i++)
{
int t = q.front();
q.pop();
int left_child = t * 2, right_child = t * 2 + 1;
if (left_child <= max_no)q.push(left_child);
if (right_child <= max_no)q.push(right_child);
printf("%d ", tree[t]);
}
printf("\n");
}
printf("tree end\n");
}
int main()
{
freopen("D:/c++/dev-data/in.txt", "r", stdin);
int o, l, r, d;
while(~scanf("%d %d", &n, &m))
{
memset(tree, 0, sizeof(tree));
memset(mark, 0, sizeof(mark));
memset(raw_data, 0, sizeof(raw_data));
for (int i = 1; i <= n; ++i)
scanf("%d", &raw_data[i]);
build(1, 1, n);
//PrintTree();
while (m--)
{
scanf("%d %d %d", &o, &l, &r);
if (o == 1)
{
scanf("%d", &d);
update(1, 1, n, l, r, d);
}
else
{
printf("%lld\n", query(1, 1, n, l, r));
}
//PrintTree();
}
}
return 0;
}