线段树 模板题

线段树

Problem Description
已知一个数列,你需要进行下面两种操作:
1.将某区间每一个数加上x
2.求出某区间每一个数的和
Input
第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。
第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。
接下来M行每行包含3或4个整数,表示一个操作,具体如下:
操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k
操作2: 格式:2 x y 含义:输出区间[x,y]内每个数的和
Output
包含若干行整数,即为所有操作2的结果。
Sample Input
5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4
Sample Output
11
8
20
Tip
时空限制:1000ms,128M
Data Constraint
对于30%的数据:N≤8,M≤10
对于70%的数据:N≤1000,M≤10000
对于100%的数据:N≤100000,M≤100000

刚刚看到本题,很显然,我们可以先尝试一下暴力,当然,如果分块的话,我们也可以大大加快我们对本题的处理。
那么我们就有非常简单的暴力代码。

#include 

#define LL long long 

using namespace std;

LL n,m;
LL a[1000001];

int main()
{
	scanf("%lld%lld",&n,&m);
	for (LL i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	for (LL i=1;i<=m;i++)
	{
		LL k,x,y,j;
		scanf("%lld%lld%lld",&j,&x,&y);
		switch (j){
			case 1:
				scanf("%lld",&k);
				for (LL p=x;p<=y;p++)
					a[p]+=k;
				break;
			case 2:
				{
					LL ans=0;
					for (LL p=x;p<=y;p++)
						ans+=a[p];
					printf("%lld\n",ans);
				}
				break;
		}
	}
}

观察本题,很显然我们需要建立一棵线段树,其每个节点都存储一段区间的和, 并且我们可能需要一个布尔函数来帮助我们快速修改一段区间的值。当然,如果题目只是一个值一个值修改的话,我们可以选择树状数组来更快地处理问题。
那么,什么是线段树呢?

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。

简单来说,线段树的建立,一般是将一个数组tree[],每一个编号i都算一个节点,那么我们就可以得到任意一个线段树的节点 tree[i],并且使用结构体的方式存储。
这样,我们的tree[i],就可以记录下它存储的区间和的左端点 left 和右端点 right,以及这段区间的和 sum。同时,线段树之所以是线段树,是因为它的祖先、儿子存在包含关系。
简单来说,如果我已经有一个节点tree[i],那么我们通过它所存储的区间的中点 mid=(left+right)/2 ,那么我可以再为这个节点 tree[i] 记录两个值 leftson , rightson ,表示它的左儿子和右儿子,那么,左右儿子分别继承父亲节点的一部分区间和,为了尽量降低复杂度,我们选择左右儿子分别继承父亲节点的一半区间,所得到的左儿子 tree[leftson] ,它的区间左端点仍为 left , 右端点变成了mid 。所以右儿子 tree[rightson],它的区间左端点则为 mid+1 ,右端点则是 right 。由此我们明白,左右儿子所记录的区间和就是父亲节点记录的区间和,那么我们就可以通过这一点,进行一个搜索。
我们从根节点进行搜索一段区间,判断这段区间是继承到了左儿子还是右儿子处,也可能是两个儿子分别占有一部分,然后分别进行搜索,就可以得到答案。
也正是因为每个节点记录的是一段区间和,所以我们修改一段区间的数的大小时,我们只需一个节点一个节点处理就可以了,当然,为了节约时间,我们可以引入变量 change 来记录这个节点的所有儿子和它是否需要修改,然后在我们遍历到存在change 的点时,我们只需要增加该节点的和,并将change继承给它的各个儿子,就可以了。
所以,我们有如下代码。

#include 
 
#define LL long long  
 
using namespace std;
 
LL n,m;
LL q[100010];
struct node{
    LL left,right,sum,change;
}v[270000];
 
void build(LL ,LL ,LL );
void change(LL ,LL ,LL ,LL );
LL find(LL ,LL ,LL );
 
int main()
{
    scanf("%lld%lld",&n,&m);
    for (LL i=1;i<=n;i++)
        scanf("%lld",&q[i]);
    build(1,1,n);
    for (LL i=1;i<=m;i++)
    {
        LL j,x,y,k;
        scanf("%lld%lld%lld",&j,&x,&y);
        if (j==1)
        {
            scanf("%lld",&k);
            change(1,x,y,k);
        }
        else
            printf("%lld\n",find(1,x,y));
    }
    return 0;
}
 
LL find(LL ro,LL le,LL ri)
{
    if (v[ro].change)
    {
        v[ro].sum+=v[ro].change*(v[ro].right-v[ro].left+1);
        if (v[ro].left>1;
    if (ri<=mid)
        return find(ro<<1,le,ri);
    if (le>mid)
        return find(ro<<1|1,le,ri);
    return find(ro<<1,le,mid)+find(ro<<1|1,mid+1,ri);
}
 
void change(LL ro,LL le,LL ri,LL ca)
{
    v[ro].sum+=(ri-le+1)*ca;
    if (le==v[ro].left&&ri==v[ro].right)
    {
        if (v[ro].left>1;
    if (ri<=mid)
    {
        change(ro<<1,le,ri,ca);
        return ;
    }
    if (le>mid)
    {
        change(ro<<1|1,le,ri,ca);
        return ;
    }
    change(ro<<1,le,mid,ca);
    change(ro<<1|1,mid+1,ri,ca);
    return ;
}
 
void build(LL ro,LL le,LL ri)
{
    v[ro].left=le;v[ro].right=ri;
    if (le>1;
        build(ro<<1,le,mid);
        build(ro<<1|1,mid+1,ri);
        v[ro].sum=v[ro<<1].sum+v[ro<<1|1].sum;
    }
    else
        v[ro].sum=q[le];
    return ;
}

你可能感兴趣的:(线段树 模板题)