线段树orz!
前天我A过了我人生中第一道线段树题!!!【鼓掌٩(๑>◡<๑)۶】【撒花✿✿ヽ(°▽°)ノ✿】QwQ(虽然这道题我之前就用树状数组水过了咳咳咳)
好啦进入正题(*´▽`)ノノ
线段树
线段树是啥请自行度娘线段树的百度百科hhh,线段树的优势很明显啊,就是一个字 强!!!树状数组必须满足逆元,例如求和之类因为树状数组本质上维护的是前缀和操作,区间操作实质上是右端点的前缀和-左端点-1的前缀和,所以最大值,最小值这类问题就不能解决,但是线段树不需要啊!!!强的很!!!缺点更明显QwQ,代码贼长,常数贼大(咳咳咳蒟蒻我还没学到zkw线段树,大佬勿喷),线段树必须要满足区间加法,emm我对于区间加法的理解就是可以通过左儿子和右儿子的状态转移到父节点的状态吧qwq,下面上图(图来自度娘)
大概就是以上操作啦,剩下的算法思想在线段树操作具体实现里再说
算法思想&实现
我我我先贴代码再说算法思想昂QwQ
建树
void pushup(int ha)//加法操作 ,对,很简单,就是懒而已
{
segtree[ha]=segtree[ha*2]+segtree[ha*2+1];
}
void build(int l,int r,int rt)//建树
{
if (l==r)//如果已经到叶子节点,就直接赋值
{
segtree[rt]=a[l];
return ;//找到叶子节点后回溯
}
int mm=(l+r)>>1;//mm代表的是l,r区间中的中点
build(l,mm,rt*2);//再分别以左儿子和右儿子再进行建树操作
build(mm+1,r,rt*2+1);
pushup(rt);
}
上面这个就是建树操作了,l,r表示当前区间的左端点和右端点,rt是当前节点的编号
(上面的pushup函数是跟某大佬学的,在此远远%%%岩之痕大佬!%%%裂墙推荐大佬的博客啊!!我就是照着大佬的博客自学的,我自己这篇博客纯粹是为了自己理解QwQ)
各位照着上图凑合看吧,我懒得自己画图了~qwq
首先从【1,10】开始建树,判断当前节点是不是叶子节点,如果是就直接赋值后回溯,不是的话就往下用左儿子和右儿子继续建树
单点修改
其实有点多余,但还是写上吧~
void modify(int l,int r,int c,int kk,int rt)//单点修改
{
if (l==r)
{
segtree[rt]+=kk;
}
int mm=(l+r)>>1;
if (c<=mm)
modify(1,mm,c,kk,rt*2);
else
modify(mm+1,r,c,kk,rt*2+1);
pushup(rt);
}
这个代码还是比较好理解吧……(没底气)详细的解释在后面的区间修改
区间修改
void pushdown(int rt,int ln,int rn)
{
if (add[rt]!=0)
{
segtree[rt*2]+=ln*add[rt];
segtree[rt*2+1]+=rn*add[rt];
add[rt*2]+=add[rt];
add[rt*2+1]+=add[rt];
add[rt]=0;
}
}
void update(int l,int r,int L,int R,int c,int rt)//区间修改
{
if (l>=L&&r<=R)
{
segtree[rt]+=c*(r-l+1);
add[rt]+=c;
return;
}
int mm=(l+r)>>1;
pushdown(rt,mm-l+1,r-mm);
if (L<=mm)
update(l,mm,L,R,c,rt*2);
if (R>mm)
update(mm+1,r,L,R,c,rt*2+1);
pushup(rt);
}
[l,r]表示当前区间[L,R]是修改区间,c是修改值,rt为当前节点
pushdown函数就是一个下推标记,每次修改完或查询前就下推一下当前节点,看看左儿子和右儿子的值有没有变,也就是说,有一些需要修改但是查询时没有影响的节点我们可以先不修改,等到查询到它的时候再一并修改,这是线段树的精华所在呐!!!
add数组就是记录一下当前节点修改后需要下推给其他节点的值,所以下推后当前节点的add值要清零,防止下次重复下推。
update函数是区间修改的函数了,跟单点修改不同的是,区间修改的判断不是是否为叶子节点,而是当前区间是否完全包含在修改区间中,如果是,那么直接修改后回溯,如果不是,再分别update左儿子和右儿子(前提是左儿子or右儿子与修改区间有重合)
等再回溯回某节点时,它的儿子孙子们(子节点)就已经修改完了,这时我们就可以用儿子(子节点)的状态把爸爸(父节点)求出来了哈哈哈哈
区间查询
long long query(int l,int r,int L,int R,int rt)//区间查询
{
if (l>=L&&r<=R)
return segtree[rt];
int mm=(l+r)>>1;
pushdown(rt,mm-l+1,r-mm);
long long ANS=0;
if (L<=mm)
ANS+=query(l,mm,L,R,rt*2);
if (R>mm)
ANS+=query(mm+1,r,L,R,rt*2+1);
return ANS;
}
区间查询其实跟区间修改还有点像,如当前区间完全在查询区间中,那么就直接返回当前节点的值,否则就继续query左儿子和右儿子,其间注意下推一次标记,保证接下来查询的区间已经修改过
贴AC代码(这题数据范围有毒)
https://www.luogu.org/problemnew/show/P3372
#include<bits/stdc++.h>
using namespace std;
long long segtree[401010],a[100100],add[401010];
void pushup(int ha)//加法操作
{
segtree[ha]=segtree[ha*2]+segtree[ha*2+1];
}
void build(int l,int r,int rt)//建树
{
if (l==r)
{
segtree[rt]=a[l];
return;
}
int mm=(l+r)>>1;
build(l,mm,rt*2);
build(mm+1,r,rt*2+1);
pushup(rt);
}
void modify(int l,int r,int c,int kk,int rt)//单点修改,本题中多余
{
if (l==r)
{
segtree[rt]+=kk;
}
int mm=(l+r)>>1;
if (c<=mm)
modify(1,mm,c,kk,rt*2);
else
modify(mm+1,r,c,kk,rt*2+1);
pushup(rt);
}
void pushdown(int rt,int ln,int rn)
{
if (add[rt]!=0)
{
segtree[rt*2]+=ln*add[rt];
segtree[rt*2+1]+=rn*add[rt];
add[rt*2]+=add[rt];
add[rt*2+1]+=add[rt];
add[rt]=0;
}
}
void update(int l,int r,int L,int R,int c,int rt)//区间修改
{
if (l>=L&&r<=R)
{
segtree[rt]+=c*(r-l+1);
add[rt]+=c;
return;
}
int mm=(l+r)>>1;
pushdown(rt,mm-l+1,r-mm);
if (L<=mm)
update(l,mm,L,R,c,rt*2);
if (R>mm)
update(mm+1,r,L,R,c,rt*2+1);
pushup(rt);
}
long long query(int l,int r,int L,int R,int rt)//区间查询
{
if (l>=L&&r<=R)
return segtree[rt];
int mm=(l+r)>>1;
pushdown(rt,mm-l+1,r-mm);
long long ANS=0;
if (L<=mm)
ANS+=query(l,mm,L,R,rt*2);
if (R>mm)
ANS+=query(mm+1,r,L,R,rt*2+1);
return ANS;
}
int main()
{
int m,n;
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
scanf("%d",&a[i]);
build(1,n,1);
for (int i=1;i<=m;i++)
{
int p;
scanf("%d",&p);
if (p==1)
{
int k,pl,pr;
scanf("%d%d%d",&pl,&pr,&k);
update(1,n,pl,pr,k,1);
}
if (p==2)
{
int pl,pr,ans;
scanf("%d%d",&pl,&pr);
printf("%lld\n",query(1,n,pl,pr,1));
}
}
return 0;
}
完结撒花✿✿ヽ(°▽°)ノ✿!