标签:代码 前言 拓展 资料 display type typename 信息 数据结构
因为用来装 Win To Go 的硬盘炸了,所以今天下午就水一个博客罢。
下面是目录:
首先我们需要知道线段树是用来解决什么问题的数据结构。
先看她的名字来进行大胆的猜测:
线段树,顾名思义,是和线段有关的树,那么其实线段树就是一种维护区间信息的数据结构。
和树状数组的基本结构类似,线段树也是把一个序列分为各种大小的区间,再按照相应的包含关系构成一棵树。
说的不是很清楚?上图看看。
我们可以清晰的看到最小的节点是长度为 1 的区间,她的父节点都是包含她的更大的区间。显然的是对于长度为 \(n\) 的区间,节点总个数为 \(2n\)。
在传统递归建树下,我们建树的复杂度是 \(O(n\log n)\) 的。
这里是堆式储存,我们要开 \(4n\) 的数组,至于缘由详见这一篇文章:线段树为什么要开4倍空间 - 拾月凄辰 - 博客园 (cnblogs.com)。
如果您不想去看,我在这里大约简述一下:因为我们建树的时候建出来的不一定是一棵满二叉树,甚至都不是一颗完全二叉树,因此 \(2n\) 的数组空间肯定不够用。实际上我们最大节点的编号是在 \(3n\) 和 \(4n\) 之间的,因此我们开 \(4n\) 的数据才能保证不会发生越界。
同时,还有一种建树的方式叫做动态开点。理论上动态开点的时间复杂度也是 \(O(n\log n)\) 的,只是动态开点能够节省数组空间至 \(2n\)。
下面我来结合代码讲解如何实现线段树的基本结构。
我先会对每一个操作进行实现,然后再给出全部程序。
这里使用的莉题是 浴谷 P3373 【模板】线段树 2 。
因为我的缺省源很长,所以为了节约篇幅,就先把缺省源放在这里。
#define Heriko return
#define Deltana 0
#define S signed
#define LL long long
#define R register
#define I inline
#define lc(x) (x<<1)
#define rc(x) (x<<1|1)
#define CI const int
#define mst(a, b) memset(a, b, sizeof(a))
using namespace std;
template<typename T>
I void fr(T &x) {...//快读}
template<typename T>
I void fw(T x,bool k) {...//快输}
要致富,先建树。
线段树,先建树。
为了方便,这里我是用的结构体来记录节点,\(\texttt{Pushup}\) 函数是合并信息。
struct node
{
LL val,add,mul;
}
t[MXX];
I void pushup(LL rt)
{
t[rt].val=(t[rc(rt)].val+t[lc(rt)].val)%mod;
}
由于我本篇的剩余代码基本都是按照传统的递归建树写的,因此这里的建树的方法就是传统递归建树。
void build(LL rt,LL l,LL r)
{
t[rt].add=0;t[rt].mul=1;//区间加tag 和 区间乘tag
if(l==r) {fr(t[rt].val);Heriko;}
LL mid=(l+r)>>1;
build(lc(rt),l,mid);
build(rc(rt),mid+1,r);
pushup(rt);
}
至于动态开点,可以自行 BDFS。这里提供一个我觉得讲的还可以的:算法学习笔记(49): 线段树的拓展 - 知乎 (zhihu.com)。
简单解释一下上面的代码。先是把 tag 清零,然后进行二分继续建树,最后上传区间值。
考虑到直接对区间内的每一个数进行加法是非常慢的,所以我们用到一个叫做 \(\texttt{Lazy Tag}\) 的东西来优化时间复杂度。当我们要查询 / 进行操作的时候再把 Tag 下传,这样在每次查询 / 修改之前,每个节点的值都已经及时的更新完毕。
因为这道题是有区间加法和区间乘法两个操作,所以我们要维护两个 Tag。
要注意的是要先处理乘法 Tag 再处理加法 Tag。
I void pushdown(LL rt,LL l)
{
t[lc(rt)].val=(t[lc(rt)].val*t[rt].mul+t[rt].add*(l-(l>>1)))%mod;
t[rc(rt)].val=(t[rc(rt)].val*t[rt].mul+t[rt].add*(l>>1))%mod;
(t[lc(rt)].mul*=t[rt].mul)%=mod;
(t[rc(rt)].mul*=t[rt].mul)%=mod;
t[lc(rt)].add=(t[lc(rt)].add*t[rt].mul+t[rt].add)%mod;
t[rc(rt)].add=(t[rc(rt)].add*t[rt].mul+t[rt].add)%mod;
t[rt].add=0;t[rt].mul=1;
}
实际上就是打上标记然后二分递归完事~
void mul(LL rt,LL l,LL r,LL x,LL y,LL val)
{
if(x<=l and r<=y)
{
(t[rt].val*=val)%=mod;
(t[rt].mul*=val)%=mod;
(t[rt].add*=val)%=mod;
Heriko;
}
pushdown(rt,r-l+1);
LL mid=(l+r)>>1;
if(x<=mid) mul(lc(rt),l,mid,x,y,val);
if(y>mid) mul(rc(rt),mid+1,r,x,y,val);
pushup(rt);
}
要注意在继续递归前先 \(\texttt{Pushdown}\),以及在递归完成后 \(\texttt{Pushup}\),否则你会像我一样调亿年。
实际上区间加的操作和区间乘基本上是一致的。
void add(LL rt,LL l,LL r,LL x,LL y,LL val)
{
if(x<=l and r<=y)
{
(t[rt].add+=val)%=mod;
(t[rt].val+=val*(r-l+1))%=mod;
Heriko;
}
pushdown(rt,r-l+1);
LL mid=(l+r)>>1;
if(x<=mid) add(lc(rt),l,mid,x,y,val);
if(y>mid) add(rc(rt),mid+1,r,x,y,val);
pushup(rt);
}
因为是查询值所以我们一样去递归的累加子树值即可。
LL query(LL rt,LL l,LL r,LL x,LL y)
{
if(x<=l and r<=y) Heriko t[rt].val%mod;
pushdown(rt,r-l+1);
LL mid=(l+r)>>1;
LL ans=0;
if(x<=mid) ans+=query(lc(rt),l,mid,x,y)%mod;
if(y>mid) ans+=query(rc(rt),mid+1,r,x,y)%mod;
Heriko ans%mod;
}
最后就是全部代码,实际上如果整体来看,线段树唯一要记住的就是 \(\texttt{Pushdown}\) 的代码,其余的就都是很自然的递归修改,递归统计。
...//缺省源放在上面了
CI MXX=1e6+5;
LL n,m,mod;
struct node
{
LL val,add,mul;
}
t[MXX];
I void pushup(LL rt)
{
t[rt].val=(t[rc(rt)].val+t[lc(rt)].val)%mod;
}
void build(LL rt,LL l,LL r)
{
t[rt].add=0;t[rt].mul=1;
if(l==r) {fr(t[rt].val);Heriko;}
LL mid=(l+r)>>1;
build(lc(rt),l,mid);
build(rc(rt),mid+1,r);
pushup(rt);
}
I void pushdown(LL rt,LL l)
{
t[lc(rt)].val=(t[lc(rt)].val*t[rt].mul+t[rt].add*(l-(l>>1)))%mod;
t[rc(rt)].val=(t[rc(rt)].val*t[rt].mul+t[rt].add*(l>>1))%mod;
(t[lc(rt)].mul*=t[rt].mul)%=mod;
(t[rc(rt)].mul*=t[rt].mul)%=mod;
t[lc(rt)].add=(t[lc(rt)].add*t[rt].mul+t[rt].add)%mod;
t[rc(rt)].add=(t[rc(rt)].add*t[rt].mul+t[rt].add)%mod;
t[rt].add=0;t[rt].mul=1;
}
void mul(LL rt,LL l,LL r,LL x,LL y,LL val)
{
if(x<=l and r<=y)
{
(t[rt].val*=val)%=mod;
(t[rt].mul*=val)%=mod;
(t[rt].add*=val)%=mod;
Heriko;
}
pushdown(rt,r-l+1);
LL mid=(l+r)>>1;
if(x<=mid) mul(lc(rt),l,mid,x,y,val);
if(y>mid) mul(rc(rt),mid+1,r,x,y,val);
pushup(rt);
}
void add(LL rt,LL l,LL r,LL x,LL y,LL val)
{
if(x<=l and r<=y)
{
(t[rt].add+=val)%=mod;
(t[rt].val+=val*(r-l+1))%=mod;
Heriko;
}
pushdown(rt,r-l+1);
LL mid=(l+r)>>1;
if(x<=mid) add(lc(rt),l,mid,x,y,val);
if(y>mid) add(rc(rt),mid+1,r,x,y,val);
pushup(rt);
}
LL query(LL rt,LL l,LL r,LL x,LL y)
{
if(x<=l and r<=y) Heriko t[rt].val%mod;
pushdown(rt,r-l+1);
LL mid=(l+r)>>1;
LL ans=0;
if(x<=mid) ans+=query(lc(rt),l,mid,x,y)%mod;
if(y>mid) ans+=query(rc(rt),mid+1,r,x,y)%mod;
Heriko ans%mod;
}
LL x,l,r,val;
S main()
{
fr(n),fr(m),fr(mod);
build(1,1,n);
while(m--)
{
fr(x);
if(x==1) {fr(l),fr(r),fr(val);mul(1,1,n,l,r,val);}
else if(x==2) {fr(l),fr(r),fr(val);add(1,1,n,l,r,val);}
else {fr(l),fr(r);fw(query(1,1,n,l,r)%mod);}
}
Heriko Deltana;
}
在这篇完稿时,开头所说的装 Win To Go 的硬盘能用了,也算是首尾呼应罢(确信)。
标签:代码 前言 拓展 资料 display type typename 信息 数据结构
原文地址:https://www.cnblogs.com/HRiver2/p/HR2note31.html