You have N integers, A1,A2,...,AN . You need to deal with two kinds of operations. One type of operation is to add some given number to each number in a given interval. The other is to ask for the sum of numbers in a given interval.
初学线段树,看了两天终于找到了一点感觉,自己写了一个板子,修修改改竟然也AC了三道题,决定把心得和代码写进博客,有错误的地方还请指出来哦(如果有人看的话)。第一次写博客,可能会很啰嗦0.0。
线段树的思想,在一棵二叉树上,每一层都是一个完整的区间,随着层数加深,每个区间不断被划分为两个更小的区间,分别交给两个子结点维护。对某一范围的查询,只需要少数内部结点维护的区间就包含了这个范围的所有信息。
题意:含N个整数
A1,A2,...,An
的序列,对其有Q次询问,每次询问都是下面两个操作的一个
Q l r 求
Al
~
Ar
所有整数的和
C l r c 将
Al
~
Ar
所有整数加c
这道题看上去并不复杂,主要就是卡查询和更新整个树的时间,也就是说要尽量减少每次查询和更新的基本操作次数。
查询操作很好处理,主要是处理区间结点的值的更新很容易超时。如果每次对所有区间结点更新,可能将近一半的更新其实都没用到,因为很多查询,用离根结点较近的区间结点就能组合出答案,不需要访问到的底层结点。所以可以偷个懒,从根节点往下更新时,遇到一个区间结点就把它的lzy设置为要增加的值,就把它以及它的所有子节点的更新停在lzy上面,等到下次需要查询它的子节点的时候,再继续向要查询的那个结点更新。
以[1~5]的线段树作为例子解释一下三个函数:
ll build(int l, int r , int i)
1.从根结点开始,不断的把大区间分成左右两个子区间,交给两个子结点处理。
2.在遇到叶子节点时进行一次输入(后序遍历使得每个输入放到恰当的位置)。每个结点设置完以后返回的是它的区间的和。
ll qsum(int ql, int qr, int i )
1.首先查看结点i是否有lzy,如果有就更新结点i的sum,并把lzy传递给子结点。
2.把范围的求和分散到区间上。从根节点开始,比较范围[ql, qr] 和结点i对应的区间[l, r]。如果[l, r]不是[ql, qr],就根据[ql, qr]在[l, r]的左半部分、中间还是右半部分,在i的子结点中继续寻找。因为[l, r]包含了[ql , qr],一定能找出一个子结点的组合把范围和[ql, qr]求出来。
如果觉得难以想象的话可以把代码中//#define TEST //测试模式
这句的注释去掉,程序会打印每次寻找时经过的区间
比如求 Q 1 4 时
先和[1~5]比较,然后将[1,4]分为[1,3]和[4,4],在左子结点中寻找[1,3],在又子结点中寻找[4,4]。然后在[4,5]的左子结点继续寻找[4,4]。
ll cadd(int cl,int cr,int c int i)
1.和qsum有点类似,要把范围上的更新分散到区间结点上,但有一点不一样,不仅要找出分散到哪些区间上,这些区间的父区间也应更新。
比如执行C 1 4 1时,不仅[1,3]和[4,4]需要更新,[1,5]和[4,5]也需要更新。其实就是把经过的所有区间的sum更新就行了 。需要注意的是,[1,3]和[4,4]的更新体现在把lzy设置为要增加的值1,而父结点的更新直接更新sum就行了。
下面是ac代码
宏left(i) right(i) 分别得到代表结点的左右子结点的编号
mid(l,r)得到区间[l,r]的中点
#include <stdio.h>
#define maxn 100005
typedef long long ll;
//#define TEST //测试模式
#define left(i) (i<<1)
#define right(i) ((i<<1)+1)
#define mid(l,r) ((l) + (((r) -(l)) >> 1))
int N, Q;
class xds
{
public:
xds() :lzy(0), sum(0), l(0), r(0) {}
ll lzy;//等待更新此结点的sum和它字结点的lzy
ll sum;
int l;
int r;
};
#ifdef TEST
xds node[30];
#else
xds node[maxn << 2];
#endif // TEST
void renew(int i) {
if (node[i].l == node[i].r) {
node[i].sum += node[i].lzy;
node[i].lzy = 0; return;
}
node[i].sum += (node[i].r - node[i].l + 1)*node[i].lzy;
node[left(i)].lzy += node[i].lzy;
node[right(i)].lzy += node[i].lzy;
node[i].lzy = 0;
}
ll build(int l, int r, int i) {
#ifdef TEST
printf("访问区间%d %d\n", l, r);
#endif // TEST
node[i].l = l;
node[i].r = r;
if (r == l) {
scanf("%lld", &node[i].sum);
return node[i].sum;
}
return node[i].sum = build(l, mid(l,r), left(i)) + build(mid(l,r) + 1, r, right(i));
}
ll qsum(int ql, int qr, int i) {
ll temp;
int l = node[i].l;
int r = node[i].r;
#ifdef TEST
printf("访问区间%d %d\n", l, r);
#endif // TEST
if (node[i].lzy)
renew(i);
if (l == ql&&r == qr) {
return node[i].sum;
}
if (qr <= mid(l, r))
{
return qsum(ql, qr, left(i));
}
if (ql <= mid(l, r))
{
temp = qsum(ql, mid(l, r), left(i)) + qsum(mid(l, r) + 1, qr, right(i));
return temp;
}
temp = qsum(ql, qr, right(i));
return temp;
}
void cadd(int cl, int cr, ll c, int i) {
ll temp;
int l = node[i].l;
int r = node[i].r;
#ifdef TEST
printf("访问区间%d %d\n", l, r);
#endif // TEST
if (cl == l&&cr == r) {
node[i].lzy += c; return;
}
node[i].sum += (cr - cl + 1)*c;
if (cr <= mid(l, r))
{
cadd(cl, cr, c, left(i));
return;
}
if (cl <= mid(l, r))
{
cadd(cl, mid(l, r), c, left(i));
cadd(mid(l, r) + 1, cr, c, right(i));
return;
}
cadd(cl, cr, c, right(i));
return;
}
int main() {
#ifdef TEST
printf("数据量N 询问次数Q: ", maxn);
#endif //TEST
scanf("%d %d", &N, &Q);
#ifdef TEST
printf("建立%d个数据的线段树,输入%d个数据: \n", N, N);
#endif //TEST
build(1, N, 1);
#ifdef TEST
printf("建树完毕,输入Q l r求区间和,输入C l r c增加区间内所有数,输入E退出\n");
#endif // TEST
char temp[2];
ll res;
int l, r;
ll c;
for (int kase = 0; kase < Q; ++kase) {
scanf("%s", &temp);
if (temp[0] == 'Q') {
scanf("%d %d", &l, &r);
res = qsum(l, r, 1);
#ifdef TEST
printf("求和结果为: ");
#endif // TEST
printf("%lld\n", res);
}
else if (temp[0] == 'C') {
scanf("%d %d %lld", &l, &r, &c);
cadd(l, r, c, 1);
}
}
}