呼~好像好难的样子。。
线段树,又称区间树,给1到n个点,每个点存入了一些信息,用[ L,R 表示下标从L到R的区间信息
原理:将 [1,n] 分解成若干特定子区间(数量<=4*n),后将每一个区间 [L,R] 分解成少量特定子区间,通过对这些少量子区间的修改或统计来实现对[L,R] 的修改或统计
线段树是一棵二叉树,是平衡二叉树,但不是完全二叉树
属性:
1、每个区间的长度是区间诶正数的个数
2、叶子节点程度为1,不可再分
3、[a,b] 对应子区间:[ a,(a+b)/2 ],[ (a+b)/2+1,b ]
4、线段树高度=[log2(b-a+1)]+1
5、线段树把区间上的热议一条现代都分成不超过2logN条——任一区间分成节点,每一层超过2个
线段树定义
f1:
#define maxn 100007
int SegTree[maxn>>2];//线段树
//int Lazy[maxn>>2];//延迟更新标记
int A[maxn];//原始数组
f2:(常用)——方便添加元素
int a[N];//原始数组
struct tr
{
ll left,right;
ll num;//节点值
ll lazy;//延迟更新标记
}tree[N<<2];//线段树
线段树构造
void build(int rt,int l,int r)
{//构造根为rt,区间为[l,r]的线段树
tree[rt].left=l;
tree[rt].right=r;
tree[rt].lazy=0;
if(tree[rt].left==tree[rt].right)
{//确认过眼神,她是叶子
tree[rt].num=a[tree[rt].right];
return ;
}
int mid=(r+l)>>1;
build(rt<<1,l,mid);//递归构造左子树
build(rt<<1|1,mid+1,r);//递归构造右子树
tree[rt].num=tree[rt<<1].num+tree[rt<<1|1].num;
//回溯,向上更新
return ;
}
单点更新
void my_plus(int rt,int dis,int k)
{//单点更新
tree[rt].num+=k;//更新
if(tree[rt].left==tree[rt].right)
return ;
if(dis<=tree[rt<<1].right)
my_plus(rt<<1,dis,k);//递归更新左子树
if(dis>=tree[rt<<1|1].left)
my_plus(rt<<1|1,dis,k);//递归更新右子树
}
区间查询
void search(int rt,int l,int r)
{//区间查询
if(tree[rt].left>=l&&tree[rt].right<=r)
{
ans+=tree[rt].num;
return ;
}
if(tree[rt<<1].right>=l)
search(rt<<1,l,r);
if(tree[rt<<1|1].left<=r)
search(rt<<1|1,l,r);
//遍历左右子树与[L,R]重叠部分
}
区间更新
更新所有叶子节点,若一次性更新完,需要时间复杂度O(n),故引入“延迟标记”
延迟标记:每个节点上新增一个标记,记录这个节点是否进行了某种修改(这种操作会影响子节点),对于任意区间的修改,先按区间查询方式划分成线段树中的节点,然后修改这些节点的信息,并给这些节点标记上代表这种修改的标记
在修改和查询时,若到一个节点P,并决定是否考虑其他节点,那么需要看P是否被标记,若有则要按照标记修改其子节点的信息,并给子节点标上相同标记,同时删除P点标记
延迟更新
懒惰理由:1、多次更新,一次下推;2、无需要,不下推
懒惰原则:可懒惰,但不可出错
线段树定义:struct中加入 int lazy;
线段树构造:build函数中(初始化延时标记为0,SegTree[rt].lazy=0)
区间更新
void addplus(int rt,int l,int r,int k)
{
if(l<=tree[rt].left&&r>=tree[rt].right)
{
tree[rt].lazy+=k;//懒惰值更新
tree[rt].num+=(tree[rt].right-tree[rt].left+1)*k;
//更新数字和,向上保证正确
return ;
}
if(tree[rt].lazy)
pushdown(rt);//下推以后,才准确更新子节点
if(l<=tree[rt<<1].right)
addplus(rt<<1,l,r,k);
if(r>=tree[rt<<1|1].left)
addplus(rt<<1|1,l,r,k);
tree[rt].num=tree[rt<<1].num+tree[rt<<1|1].num;//回溯,更新本节点信息
}
PushDown函数
void pushdown(ll rt)
{
tree[rt<<1].lazy+=tree[rt].lazy;
tree[rt<<1|1].lazy+=tree[rt].lazy;
tree[rt<<1].num+=tree[rt].lazy*(tree[rt<<1].right-tree[rt<<1].left+1);
tree[rt<<1|1].num+=tree[rt].lazy*(tree[rt<<1|1].right-tree[rt<<1|1].left+1);
tree[rt].lazy=0;//一定要记得清除标记!!!
return ;
}
区间查询
void search(ll rt,ll l,ll r)
{
if(tree[rt].left>=l&&tree[rt].right<=r)
{
ans+=tree[rt].num;
return ;
}
if(tree[rt].lazy)
pushdown(rt);//懒惰标记下推,更新子节点
if(l<=tree[rt<<1].right)
search(rt<<1,l,r);//左子树与[L,R]重叠部分
if(r>=tree[rt<<1|1].left)
search(rt<<1|1,l,r);//右子树与[L,R]重叠部分
}
因为第四天有事,所以到了第五天才写了点题目,算是巩固一下知识吧
P3374 【模板】树状数组 1(单点更新+区间查询)
之前在树状数组文章里用了树状数组写过的,现在发现这可以用线段树来写的,同样都是单点/区间更新还有单点/区间查询,代码:
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int n,m;
int ans;
int he;//线段树高度
int input[N];//原始数组
struct node
{
int left,right;
int num;
}tree[N<<2];
void build(int left,int right,int rt)
{//构造线段树
he++;//高度++
tree[rt].left=left;
tree[rt].right=right;
if(left==right)
return ;
int mid=(left+right)>>1;
build(left,mid,rt<<1);//构造左子树
build(mid+1,right,rt<<1|1);//构造右子树
}
int add(int rt)
{
if(tree[rt].left==tree[rt].right)
{
tree[rt].num=input[tree[rt].right];
return tree[rt].num;
}
tree[rt].num=add(rt<<1)+add(rt<<1|1);
return tree[rt].num;
}
void my_plus(int rt,int dis,int k)
{//单点更新
tree[rt].num+=k;
if(tree[rt].left==tree[rt].right)
return ;
if(dis<=tree[rt<<1].right)
my_plus(rt<<1,dis,k);
if(dis>=tree[rt<<1|1].left)
my_plus(rt<<1|1,dis,k);
}
void search(int rt,int l,int r)
{//区间查询
if(tree[rt].left>=l&&tree[rt].right<=r)
{
ans+=tree[rt].num;
return ;
}
if(tree[rt<<1].right>=l)
search(rt<<1,l,r);
if(tree[rt<<1|1].left<=r)
search(rt<<1|1,l,r);
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>input[i];
build(1,n,1);
add(1);
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
if(a==1)
my_plus(1,b,c);
if(a==2)
{
ans=0;
search(1,b,c);
cout<<ans<<endl;
}
}
}
P3368 【模板】树状数组 2(区间更新+单点查询)
在掌握了模版后,这题当然也是不难的啦
直接上代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m;
int a[N];
int x,y,k;
int com;
int ans;
struct tre
{
int num;
int left;
int right;
int lazy;
}tree[N<<2];
void build(int rt,int l,int r)
{//建树
tree[rt].lazy=0;//懒惰标记初始化为0
tree[rt].left=l;
tree[rt].right=r;
if(l==r)
{
tree[rt].num=a[l];
return ;
}
int mid=(l+r)>>1;
build(rt<<1,l,mid);
build(rt<<1|1,mid+1,r);
tree[rt].num=tree[rt<<1].num+tree[rt<<1|1].num;
return ;
}
void pushdown(int rt)
{//标记下传
tree[rt<<1].lazy+=tree[rt].lazy;
tree[rt<<1|1].lazy+=tree[rt].lazy;
tree[rt<<1].num+=tree[rt].lazy*(tree[rt<<1].right-tree[rt<<1].left+1);
tree[rt<<1|1].num+=tree[rt].lazy*(tree[rt<<1|1].right-tree[rt<<1|1].left+1);
tree[rt].lazy=0;
return ;
}
void addplus(int rt,int l,int r,int k)
{//区间更新
if(tree[rt].left>=l&&tree[rt].right<=r)
{
tree[rt].lazy+=k;
tree[rt].num+=(tree[rt].right-tree[rt].left+1)*k;
return ;
}
if(tree[rt].lazy)
pushdown(rt);
if(l<=tree[rt<<1].right)
addplus(rt<<1,l,r,k);
if(r>=tree[rt<<1|1].left)
addplus(rt<<1|1,l,r,k);
tree[rt].num=tree[rt<<1].num+tree[rt<<1|1].num;//再次更新
}
void search(int rt,int k)
{//单点查询
if(tree[rt].left==tree[rt].right)
{
ans+=tree[rt].num;
return ;
}
if(tree[rt].lazy)
pushdown(rt);
if(k<=tree[rt<<1].right)
search(rt<<1,k);
if(k>=tree[rt<<1|1].left)
search(rt<<1|1,k);
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i];
build(1,1,n);
for(int i=1;i<=m;i++)
{
cin>>com;
if(com==1)
{
cin>>x>>y>>k;
addplus(1,x,y,k);//区间修改
}
if(com==2)
{
ans=0;
cin>>x;
search(1,x);
cout<<ans<<endl;
}
}
return 0;
}
P3372 【模板】线段树 1(区间更新+区间查询)
也就是把前面两个代码结合一下,不过一定要注意,查询的时候需要加上pushdown函数!!!否则,下面的节点得不到实时更新,还要设long long,不然会卡三个点。。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
#define ll long long
ll n,m;
ll com;
ll ans;
ll x,y,z;
ll a[N];//原始数组
struct tr
{
ll left,right;
ll num;//节点值
ll lazy;//延迟更新标记
}tree[N<<2];//线段树
void build(ll rt,ll l,ll r)
{//构造根为rt,区间为[l,r]的线段树
tree[rt].left=l;
tree[rt].right=r;
tree[rt].lazy=0;
if(tree[rt].left==tree[rt].right)
{//确认过眼神,她是叶子
tree[rt].num=a[tree[rt].right];
return ;
}
ll mid=(r+l)>>1;
build(rt<<1,l,mid);//递归构造左子树
build(rt<<1|1,mid+1,r);//递归构造右子树
tree[rt].num=tree[rt<<1].num+tree[rt<<1|1].num;
//回溯,向上更新
return ;
}
void pushdown(ll rt)
{
tree[rt<<1].lazy+=tree[rt].lazy;
tree[rt<<1|1].lazy+=tree[rt].lazy;
tree[rt<<1].num+=tree[rt].lazy*(tree[rt<<1].right-tree[rt<<1].left+1);
tree[rt<<1|1].num+=tree[rt].lazy*(tree[rt<<1|1].right-tree[rt<<1|1].left+1);
tree[rt].lazy=0;
return ;
}
void addplus(ll rt,ll l,ll r,ll k)
{
if(l<=tree[rt].left&&r>=tree[rt].right)
{
tree[rt].lazy+=k;
tree[rt].num+=(tree[rt].right-tree[rt].left+1)*k;
return ;
}
if(tree[rt].lazy)
pushdown(rt);
if(l<=tree[rt<<1].right)
addplus(rt<<1,l,r,k);
if(r>=tree[rt<<1|1].left)
addplus(rt<<1|1,l,r,k);
tree[rt].num=tree[rt<<1].num+tree[rt<<1|1].num;
}
void search(ll rt,ll l,ll r)
{
if(tree[rt].left>=l&&tree[rt].right<=r)
{
ans+=tree[rt].num;
return ;
}
if(tree[rt].lazy)
pushdown(rt);
if(l<=tree[rt<<1].right)
search(rt<<1,l,r);
if(r>=tree[rt<<1|1].left)
search(rt<<1|1,l,r);
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i];
build(1,1,n);
for(int i=1;i<=m;i++)
{
cin>>com;
if(com==1)
{
cin>>x>>y>>z;
addplus(1,x,y,z);
}
if(com==2)
{
cin>>x>>y;
ans=0;
search(1,x,y);
cout<<ans<<endl;
}
}
}
P3373 【模板】线段树 2(区间更新乘法+区间查询)
这题好啊!!首先是区间更新,立即就能想到懒惰标记 ~~lazy~~,然后再看题面。用上了乘法,还有加法,那么这俩同时出现的时候,我们就不得不设俩懒惰标了。再看乘法和加法混杂,那么就一定会有顺序问题,那么这里一定得是先乘后加!!!!
先把需要加的乘上倍数,那么这个需要加上的数,就被更新成新子树需要加上的数了,后来的操作只要将原来的根节点的值乘以倍数再加上lazyadd即可
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
#define ll long long
ll n,m,q;
ll a[N];
ll com,x,y,z;
ll ans;
struct tr
{
ll left;
ll right;
ll lazymul;
ll lazyadd;
ll num;
}tree[N<<2];
void build(ll l,ll r,ll rt)
{
tree[rt].left=l;
tree[rt].right=r;
tree[rt].lazyadd=0;
tree[rt].lazymul=1;
if(l==r)
{
tree[rt].num=a[l]%m;
return ;
}
ll mid=(l+r)>>1;
build(l,mid,rt<<1);
build(mid+1,r,rt<<1|1);
tree[rt].num=(tree[rt<<1].num+tree[rt<<1|1].num)%m;
return ;
}
void pushdown(ll rt)
{
tree[rt<<1].lazymul=(tree[rt<<1].lazymul*tree[rt].lazymul)%m;
tree[rt<<1|1].lazymul=(tree[rt<<1|1].lazymul*tree[rt].lazymul)%m;
tree[rt<<1].lazyadd=((tree[rt<<1].lazyadd*tree[rt].lazymul)%m+tree[rt].lazyadd)%m;
tree[rt<<1|1].lazyadd=((tree[rt<<1|1].lazyadd*tree[rt].lazymul)%m+tree[rt].lazyadd)%m;
tree[rt<<1].num=((tree[rt<<1].num*tree[rt].lazymul)%m+(tree[rt].lazyadd*(tree[rt<<1].right-tree[rt<<1].left+1))%m)%m;
tree[rt<<1|1].num=((tree[rt<<1|1].num*tree[rt].lazymul)+(tree[rt].lazyadd*(tree[rt<<1|1].right-tree[rt<<1|1].left+1))%m)%m;
tree[rt].lazyadd=0;
tree[rt].lazymul=1;
}
void multiple(ll rt,ll l,ll r,ll k)
{
if(tree[rt].left>=l&&tree[rt].right<=r)
{
tree[rt].num=(tree[rt].num*k)%m;
tree[rt].lazymul=(tree[rt].lazymul*k)%m;
tree[rt].lazyadd=(tree[rt].lazyadd*k)%m;
return ;
}
pushdown(rt);
if(l<=tree[rt<<1].right)
multiple(rt<<1,l,r,k);
if(r>=tree[rt<<1|1].left)
multiple(rt<<1|1,l,r,k);
tree[rt].num=(tree[rt<<1].num+tree[rt<<1|1].num)%m;
return ;
}
void add(ll rt,ll l,ll r,ll k)
{
if(tree[rt].left>=l&&tree[rt].right<=r)
{
tree[rt].num+=((tree[rt].right-tree[rt].left+1)*k)%m;
tree[rt].lazyadd=(tree[rt].lazyadd+k)%m;
return ;
}
pushdown(rt);
if(l<=tree[rt<<1].right)
add(rt<<1,l,r,k);
if(r>=tree[rt<<1|1].left)
add(rt<<1|1,l,r,k);
tree[rt].num=(tree[rt<<1].num+tree[rt<<1|1].num)%m;
return ;
}
void query(ll rt,ll l,ll r)
{
if(tree[rt].left>=l&&tree[rt].right<=r)
{
ans=(ans+tree[rt].num)%m;
return ;
}
pushdown(rt);
if(l<=tree[rt<<1].right)
query(rt<<1,l,r);
if(r>=tree[rt<<1|1].left)
query(rt<<1|1,l,r);
}
int main()
{
cin>>n>>q>>m;
for(ll i=1;i<=n;i++)
cin>>a[i];
build(1,n,1);//(l,r,rt)
for(ll i=1;i<=q;i++)
{
cin>>com;
if(com==1)
{
cin>>x>>y>>z;
multiple(1,x,y,z);
}
if(com==2)
{
cin>>x>>y>>z;
add(1,x,y,z);
}
if(com==3)
{
ans=0;
cin>>x>>y;
query(1,x,y);
cout<<ans%m<<endl;
}
}
return 0;
}
不开long long见祖宗。。