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 ;
}