线段树模板
线段树是处理符合结合律的高效数据结构;
线段树专门处理区间问题的;
线段树的每个节点都表示一个区间,当l==r的时候,意味着区间长度为1,也就是表元素;
线段是是一颗完全二叉树,即每个节点要么是叶子节点要么就有两个孩子节点。
线段树模板题
递归建树:
1 |
|
区间修改
单点修改是区间修改的子问题,也就是修改区间长度为1的区间。放在一起谈。
对于区间修改,我们需要引用一个tag标记;
原因:当修改区间的一个元素时,它就需要push_up来更新它的父节点,时间复杂度O(nlogn),这很低效,当我们引入tag标记时,则可以使区间更新接近O(logn);
tag标记
当某个区间被修改时,就给这个区间打上标记,当用到这个区间的子区间的时候,我们才从父节点往子节点下传更新值.来看一下代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27void fun(ll p,ll l,ll r,ll k) // p表示该节点,[l,r]表示该节点表示的区间,k表示增量
{
tag[p]=tag[p]+k; //记录增量
tree[p]=tree[p]+(r-l+1)*k; //更新当前节点的值,因为符合结合律,所以直接可以计算得
}
void push_down(ll p,ll l,ll r) //下传tag标记
{
ll mid=(l+r)>>1;
fun(lc(p),l,mid,tag[p]); //下传给左孩子
fun(rc(p),mid+1,r,tag[p]); //下传给右孩子
tag[p]=0; //下传完,恢复标记
}
void update(ll nl,ll nr,ll p,ll l,ll r,ll k) //区间修改,[nl,nr]表示修改的区间,p表示该区间的节点,[l,r]表示该节点表示的区间,k表示增量
{
if(nl<=l&&r<=nr) //当需要修改的区间完全覆盖当前区间,我们就给该区间打上tag标记,并且更新一下该节点的值
{
tag[p]+=k;
tree[p]+=(r-l+1)*k;
return;
}
if(tag[p]!=0) push_down(p,l,r); //没有完全覆盖则需要继续找,如果当前节点有tag标记,说明它的孩子节点没有被更新,我们要先下放tag标记
ll mid=(l+r)>>1;
if(nl<=mid)update(nl,nr,lc(p),l,mid,k); //更新左子树
if(nr>mid)update(nl,nr,rc(p),mid+1,r,k); //更新右子树
push_up(p); //回溯时,维护父节点
}
区间查询
1 | ll query(ll nl,ll nr,ll p,ll l,ll r) |
完整代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int MAX_N=1e6+5;
ll tree[MAX_N<<2]; //线段树的节点,因为从树顶往下建树,所以需要4N的大小;
ll a[MAX_N]; //存原数组
ll tag[MAX_N<<2]; //标记
ll N,M;
ll lc(ll x) {return x<<1;}
ll rc(ll x) {return x<<1|1;}
void push_up(ll p)
{
tree[p]=tree[lc(p)]+tree[rc(p)];
}
void fun(ll p,ll l,ll r,ll k)
{
tag[p]=tag[p]+k;
tree[p]=tree[p]+(r-l+1)*k;
}
void push_down(ll p,ll l,ll r)
{
ll mid=(l+r)>>1;
fun(lc(p),l,mid,tag[p]);
fun(rc(p),mid+1,r,tag[p]);
tag[p]=0;
}
void build(ll p,ll l,ll r) //递归建树
{
tag[p]=0;
if(l==r){
tree[p]=a[l];
return;
}
ll mid=(l+r)>>1;
build(lc(p),l,mid);
build(rc(p),mid+1,r);
push_up(p);
}
ll query(ll nl,ll nr,ll p,ll l,ll r)
{
ll res=0;
if(nl<=l&&r<=nr) return tree[p];
if(tag[p]!=0) push_down(p,l,r);
ll mid=(l+r)>>1;
if(nl<=mid)res+=query(nl,nr,lc(p),l,mid);
if(nr>mid) res+=query(nl,nr,rc(p),mid+1,r);
return res;
}
void update(ll nl,ll nr,ll p,ll l,ll r,ll k)
{
if(nl<=l&&r<=nr)
{
tag[p]+=k;
tree[p]+=(r-l+1)*k;
return;
}
if(tag[p]!=0) push_down(p,l,r);
ll mid=(l+r)>>1;
if(nl<=mid)update(nl,nr,lc(p),l,mid,k);
if(nr>mid)update(nl,nr,rc(p),mid+1,r,k);
push_up(p);
}
int main() {
scanf("%lld%lld",&N,&M);
for(int i=1;i<=N;i++) scanf("%lld",&a[i]);
build(1,1,N);
while(M--)
{
ll b;
scanf("%lld",&b);
if(b==1)
{
ll x,y,z;
scanf("%lld%lld%lld",&x,&y,&z);
update(x,y,1,1,N,z);
}
else
{
ll x,y;
scanf("%lld%lld",&x,&y);
printf("%lld\n",query(x,y,1,1,N));
}
}
return 0;
}
线段树模板2:
如题,已知一个数列,你需要进行下面三种操作:
1.将某区间每一个数乘上x
2.将某区间每一个数加上x
3.求出某区间每一个数的和
传送门
设置两个lazytag,一个存加法,一个存乘法
push_down操作的时候,人为的给这两个lazytag规定先后顺序
乘法优先:
tree[lc(p)]=(tree[lc(p)]mul[p]+add[p](mid-l+1));
tree[rc(p)]=(tree[rc(p)]mul[p]+add[p](r-mid));
1 | void push_down(ll p,ll l,ll r) |
1 |
|