线段树
比如说
有一段数
每一个数有一个值
现在有两个操作
把某个区间的所有数加上某个数
输出某个区间的和
例如
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
Output
11
8
20
如果用暴力的话
这一个样例肯定能过
但是数据一大
就T掉
所以我们用到线段树
首先是
1
−
n
1-n
1−n的区间为根节点
代表
1
−
n
1-n
1−n的区间的和
然后一分为二
如此下去
得到下图
注意:
树的性质
左儿子的编号是父亲的编号
∗
2
*2
∗2
右儿子的编号是父亲的编号
∗
2
+
1
*2+1
∗2+1
假设我们要在1-5的区间
加上2
有两种方案
一种是从下往上
把1至5的叶子节点加上2
往上加
从1到5,每一次往上加的时间复杂度为
O
(
l
o
g
2
5
+
1
)
O(log_2 5+1)
O(log25+1),五次就是
O
(
5
⋅
(
l
o
g
2
5
+
1
)
)
O(5·(log_2 5+1))
O(5⋅(log25+1))
然后得到
时间复杂度为
O
(
n
⋅
(
l
o
g
2
n
+
1
)
)
O(n·(log_2 n+1))
O(n⋅(log2n+1))
代码实现
void Add()
{
scanf("%d%d%d",&l,&r,&k);
for(int i=l;i<=r;++i)
{
int Hi=A[i].k;//A[i].k代表第i个数的节点的编号
while(Hi)
{
Tree[Hi].k+=k;
if(Hi%2)Ti--;//如果是右儿子
Hi/=2;//回到父亲节点
}
}
}
另外一种是从上往下
每个数加上
(
r
−
l
+
1
)
∗
k
(r-l+1)*k
(r−l+1)∗k
有一个需要注意
就是比如现在节点的区间为1-3
而现在的
l
l
l与
r
r
r是
1
−
1
1-1
1−1
所以不用分,直接进入左儿子
时间复杂度
O
(
l
o
g
2
n
+
1
)
O(log_2n+1)
O(log2n+1)
代码实现
//Tree[i].l与Tree[i].r为给节点的区间
//Tree[i].k为给节点的数值
void Add(int Hi,int l,int r,int k)
{
Tree[Hi].k+=(r-l+1)*k;//加值
if(l==r && Tree[Hi].l==l && Tree[Hi].r==r)return;//如果已经到了叶子结点就退出
int Mid=(Tree[Hi].l+Tree[Hi].r)/2;//
if(l<=r && r<=Mid)Add(Hi*2,l,r,k);//直接进入左儿子
else if(l>Mid && l<=r)Add(Hi*2+1,l,r,k);//进入右儿子
else
{
if(l<=Mid)Add(Hi*2,l,Mid,k);//
if(r>Mid)Add(Hi*2+1,Mid+1,r,k);//分开
}
}
那如何求值呢?
其实与加值差不多
只要当前的
l
l
l和
r
r
r与此节点的区间的范围相等
就直接
r
e
t
u
r
n
return
return本节点的值
其他都差不多
int Num(int Hi,int l,int r)
{
if(Tree[Hi].l==l && Tree[Hi].r==r)
return Tree[Hi].k;
int Mid=(Tree[Hi].l+Tree[Hi].r)/2,Ans=0;
if(l<=r && r<=Mid)Ans=Num(Hi*2,l,r);
else if(l>Mid && l<=r)Ans=Num(Hi*2+1,l,r);
else
{
if(l<=Mid)Ans+=Num(Hi*2,l,Mid);
if(r>Mid)Ans+=Num(Hi*2+1,Mid+1,r);
}
return Ans;
}
还有建树
void Tree_Buil(int Hi,int l,int r)
{
Tree[Hi].l=l;Tree[Hi].r=r;
if(l==r)Tree[Hi].k=A[l];
else
{
int Mid=(l+r)/2;
Tree_Buil(Hi*2,l,Mid);
Tree_Buil(Hi*2+1,Mid+1,r);
Tree[Hi].k=Tree[Hi*2].k+Tree[Hi*2+1].k;//父亲节点等于两个儿子加起来
}
return;
}
然后放在一起
(我加了快读)
(下面还有!)
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define ll long long
using namespace std;
struct tree_1
{
ll l,r,k;
}Tree[400250];
int A[100250];
int Operation,Sum;
int n,m,k;
int Read()
{
char c=getchar();int l=0,r=1;
while(c!='-' && c<'0' && 'c'>'9')c=getchar();
if(c=='-')r=-1;
while(c>='0' && c<='9'){l=l*10+c-'0',c=getchar();}
return l*r;
}
void Tree_Buil(int Hi,int l,int r)
{
Tree[Hi].l=l;Tree[Hi].r=r;
if(l==r)Tree[Hi].k=A[l];
else
{
int Mid=(l+r)/2;
Tree_Buil(Hi*2,l,Mid);
Tree_Buil(Hi*2+1,Mid+1,r);
Tree[Hi].k=Tree[Hi*2].k+Tree[Hi*2+1].k;
}
Sum++;
return;
}
//--------------------------------------------------------------
void Add(int Hi,int l,int r,int k)
{
Tree[Hi].k+=(r-l+1)*k;
if(l==r && Tree[Hi].l==l && Tree[Hi].r==r)return;
int Mid=(Tree[Hi].l+Tree[Hi].r)/2;
if(l<=r && r<=Mid)Add(Hi*2,l,r,k);
else if(l>Mid && l<=r)Add(Hi*2+1,l,r,k);
else
{
if(l<=Mid)Add(Hi*2,l,Mid,k);
if(r>Mid)Add(Hi*2+1,Mid+1,r,k);
}
}
//--------------------------------------------------------------
int Num(int Hi,int l,int r)
{
if(Tree[Hi].l==l && Tree[Hi].r==r)
return Tree[Hi].k;
int Mid=(Tree[Hi].l+Tree[Hi].r)/2,Ans=0;
if(l<=r && r<=Mid)Ans=Num(Hi*2,l,r);
else if(l>Mid && l<=r)Ans=Num(Hi*2+1,l,r);
else
{
if(l<=Mid)Ans+=Num(Hi*2,l,Mid);
if(r>Mid)Ans+=Num(Hi*2+1,Mid+1,r);
}
return Ans;
}
//--------------------------------------------------------------
void Write()
{
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",Num(1,l,r));
return;
}
//--------------------------------------------------------------
int main()
{
n=Read();m=Read();
for(int i=1;i<=n;++i)A[i]=Read();
Tree_Buil(1,1,n);
for(int i=1;i<=m;++i)
{
int l,r;
scanf("%d",&Operation);
if(Operation==1)
{
scanf("%d%d%d",&l,&r,&k);
Add(1,l,r,k);
}
if(Operation==2)Write();
}
return 0;
}
But
上面的程序会超时
所以我们要优化
加一个
s
t
o
r
e
store
store操作
来记录我们加的数据
就不用算到底
避免不必要的计算
当然
这只是针对
A
d
d
Add
Add(加)操作从上往下的方法
当我们需要加某一个数时
我们递归到当前
l
l
l和
r
r
r
与当前节点的区间
l
l
l和
r
r
r相等时
我们就直接把
k
k
k加到
s
t
o
r
e
store
store并退出
下次需要此节点的时候
就把
s
t
o
r
e
store
store往下延伸到儿子去
void Go(int Hi,int Len)
{
if(!Tree[Hi].store)return;//如果是0的活就没有,可以直接退出
Tree[Hi*2].store+=Tree[Hi].store;
Tree[Hi*2+1].store+=Tree[Hi].store;//加到儿子节点
Tree[Hi*2].k+=Tree[Hi].store*(Len-Len/2);
Tree[Hi*2+1].k+=Tree[Hi].store*(Len/2);//儿子的数值也要加
Tree[Hi].store=0;//清零
}
A d d Add Add
void Add(int Hi,int l,int r,int k)
{
if(Tree[Hi].l==l && Tree[Hi].r==r)
{
Tree[Hi].k+=(r-l+1)*k;
Tree[Hi].store+=k;//加到store
return;
}
if(Tree[Hi].l==Tree[Hi].r)return;
Go(Hi,Tree[Hi].r-Tree[Hi].l+1);//往下延伸
int Mid=(Tree[Hi].l+Tree[Hi].r)/2;
if(l<=r && r<=Mid)Add(Hi*2,l,r,k);
else if(l>Mid && l<=r)Add(Hi*2+1,l,r,k);
else
{
if(l<=Mid)Add(Hi*2,l,Mid,k);
if(r>Mid)Add(Hi*2+1,Mid+1,r,k);
}
Tree[Hi].k=Tree[Hi*2].k+Tree[Hi*2+1].k;
}
W r i t e Write Write
long long Num(int Hi,int l,int r)
{
if(Tree[Hi].l==l && Tree[Hi].r==r)
return Tree[Hi].k;
Go(Hi,Tree[Hi].r-Tree[Hi].l+1);//往下延伸
int Mid=(Tree[Hi].l+Tree[Hi].r)/2;
ll Ans=0;
if(l<=r && r<=Mid)Ans+=Num(Hi*2,l,r);
else if(l>Mid && l<=r)Ans+=Num(Hi*2+1,l,r);
else
{
if(l<=Mid)Ans+=Num(Hi*2,l,Mid);
if(r>Mid)Ans+=Num(Hi*2+1,Mid+1,r);
}
return Ans;
}
最后是总的代码
(下面还有!!!!)
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define ll long long
using namespace std;
struct tree_1
{
ll l,r,k,store;
}Tree[400250];
int A[100250];
int Operation,Sum;
int n,m,k;
int Read()
{
char c=getchar();int l=0,r=1;
while(c!='-' && c<'0' && 'c'>'9')c=getchar();
if(c=='-')r=-1;
while(c>='0' && c<='9'){l=l*10+c-'0',c=getchar();}
return l*r;
}
//--------------------------------------------------------------
void Go(int Hi,int Len)
{
if(!Tree[Hi].store)return;
Tree[Hi*2].store+=Tree[Hi].store;
Tree[Hi*2+1].store+=Tree[Hi].store;
Tree[Hi*2].k+=Tree[Hi].store*(Len-Len/2);
Tree[Hi*2+1].k+=Tree[Hi].store*(Len/2);
Tree[Hi].store=0;
}
void Tree_Buil(int Hi,int l,int r)
{
Tree[Hi].l=l;Tree[Hi].r=r;
if(l==r)Tree[Hi].k=A[l];
else
{
int Mid=(l+r)/2;
Tree_Buil(Hi*2,l,Mid);
Tree_Buil(Hi*2+1,Mid+1,r);
Tree[Hi].k=Tree[Hi*2].k+Tree[Hi*2+1].k;
}
return;
}
//--------------------------------------------------------------
void Add(int Hi,int l,int r,int k)
{
if(Tree[Hi].l==l && Tree[Hi].r==r)
{
Tree[Hi].k+=(r-l+1)*k;
Tree[Hi].store+=k;
return;
}
if(Tree[Hi].l==Tree[Hi].r)return;
Go(Hi,Tree[Hi].r-Tree[Hi].l+1);
int Mid=(Tree[Hi].l+Tree[Hi].r)/2;
if(l<=r && r<=Mid)Add(Hi*2,l,r,k);
else if(l>Mid && l<=r)Add(Hi*2+1,l,r,k);
else
{
if(l<=Mid)Add(Hi*2,l,Mid,k);
if(r>Mid)Add(Hi*2+1,Mid+1,r,k);
}
Tree[Hi].k=Tree[Hi*2].k+Tree[Hi*2+1].k;//加上之前没加的
}
//--------------------------------------------------------------
ll Num(int Hi,int l,int r)
{
if(Tree[Hi].l==l && Tree[Hi].r==r)
return Tree[Hi].k;
Go(Hi,Tree[Hi].r-Tree[Hi].l+1);
int Mid=(Tree[Hi].l+Tree[Hi].r)/2;
ll Ans=0;
if(l<=r && r<=Mid)Ans+=Num(Hi*2,l,r);
else if(l>Mid && l<=r)Ans+=Num(Hi*2+1,l,r);
else
{
if(l<=Mid)Ans+=Num(Hi*2,l,Mid);
if(r>Mid)Ans+=Num(Hi*2+1,Mid+1,r);
}
return Ans;
}
//--------------------------------------------------------------
int main()
{
Sum=0;
n=Read();m=Read();
for(int i=1;i<=n;++i)A[i]=Read();
Tree_Buil(1,1,n);
for(int i=1;i<=m;++i)
{
int l,r;
Operation=Read();
if(Operation==1)
{
l=Read(),r=Read(),k=Read();
Add(1,l,r,k);
}
if(Operation==2)
{
l=Read(),r=Read();
printf("%lld\n",Num(1,l,r));
}
}
return 0;
}
最后献上几张图
(两天啊!!!!!)