①首先是最普通的:区间求和并更新
int b[大小];//根据数据多少开多大的数组,储存每一个点上的值
struct zxs{
int l,r,sum;
}tree[大小*4];
void build(int id,int l,int r){
tree[id].l=l;
tree[id].r=r;
if(l==r){
tree[id].sum=b[l];//这是一个点,初始化和值就是这个点的值
return;}
int mid=(r+l)/2;
build(id*2,l,mid);//每2分为子树,id就乘以2,代表子树的编号,id*2+1为右子树编号,id*2为左子树
build(id*2+1,mid+1,r);//注意,右子树区间是mid+1开始,不包括mid
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum; //这是一个递归过程,所以这个相加的过程实际上是从最末端开始,也就是从最小的区间(点)开始,一直往前面加加加,直到整个区间都更新了和值。
}
void update(int id,int pose,int x){
int l=tree[id].l;
int r=tree[id].r;
if(tree[id].l==pose&&tree[id].r==pose){
tree[id].sum+=x;
return;} //找到了要更新的点,把要加的值给这个点的和值
int mid=(r+l)/2;
if(mid<pose)update(id*2+1,pose,x);//要找的位置在中点右边,往右子树找
else update(id*2,pose,x);//往左子树找,记住它的编号是id*2
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;//与建树的递归过程一样,从后面一直加到前面
}
int query(int id,int x,int y){//求x,y区间内的和
int l=tree[id].l;
int r=tree[id].r;
if(x<=l&&r<=y){return tree[id].su//等于也AC,不等于也AC,为什么呢?首先是从1,n开始对比,所以第一次比较如果满足该条件,那就说明x=1,y=n,否则只要在1,n内则会进行下一步,开始二分,二分之后如果还有x<=l&&r<=y,则说明一定是x<l&&y=r或x=l&&y>r,
此时返回tree[id].sum后还有一小部分是在mid左或者右(不可能左右都还有),这一小部分就已经在上一步分配个下一个子树处理了m;}
int mid=(l+r)/2;
if(y<=mid){return query(id*2,x,y);}
else if(x>mid){return query(id*2+1,x,y);}
else{//这个是当x,y区间在子树区间之内,也就是tree[id].l<l<mid<r<tree[id].r,所以直接把两个值相加
return query(id*2,x,mid)+query(id*2+1,mid+1,y);}
}
上面的查询过程需要注意的是:多了几个return,而且最后是返回数值的。
<pre name="code" class="cpp">②第二个线段树模型,懒处理操作,也就是可以更新一整个区间的和值(给一个区间,对于区间内的每个数加上一个相同的值)
然后查询任意区间内的和:
typedef long long ll;
int N,M;
ll b[101000];
struct zxs{
int l,r;
ll sum,lazy;
}tree[101000*4];
//建树过程与上面一致,不过多了一个初始化lazy=0,这个就是要给区间每个数都加的值
void build(int id,int l,int r){
tree[id].l=l;
tree[id].r=r;
tree[id].lazy=0;
if(l==r){
tree[id].sum=b[l];
return;}
int mid=(r+l)/2;
build(id*2,l,mid);
build(id*2+1,mid+1,r);
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;
}
//push_down其实就是从父树更新到它的两个子树的操作
void push_down(int id ){
if(tree[id].lazy==0)return ;(预判,避免进行不必要的操作)
tree[id*2].sum+=tree[id].lazy*(tree[id*2].r-tree[id*2].l+1);//左子树整个区间的和值都更新
tree[id*2].lazy+=tree[id].lazy;//左子树lazy值更新
tree[id*2+1].sum+=tree[id].lazy*(tree[id*2+1].r-tree[id*2+1].l+1);
tree[id*2+1].lazy+=tree[id].lazy;
tree[id].lazy=0;//当父树给它的两个子树更新完后,说明不必再进行父树的push_down操作,所以lazy变为0,但子树lazy不为0,下一次push_down的就是子树,其实lazy有点像bool标记,需要操作时lazy不为0即true,不需要操作时为0即false,后面的模型里有些bool标记跟这个类似,现在先理解,后面就很容易理解了
}
//注意题目给定的值,如果是%I64d、%lld的大数,一定要用long long
void update(int id,int x,int y,ll n){
if(tree[id].l>=x&&tree[id].r<=y){
tree[id].lazy+=n;
tree[id].sum+=n*(tree[id].r-tree[id].l+1);//原来这样才是对的
//tree[id].sum+=n*(y-x+1);这个从理解上来说是错的,但把上面的改成这样也能AC,为什么?
//经过一番思考,终于得到答案:如果一开始tree[id].l==x&&tree[id].r==y那么这样加是等效的,肯定没有错
//如果一开始x,y的区间不满足上述条件,那么可以看见在update末尾是有一个
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum,也就是最终还有一次彻底的正确的更新,
因此也不会错,但是为了规范,一定得写成正确的tree[id].sum+=n*(tree[id].r-tree[id].l+1);
return;}
push_down(id);
int l=tree[id].l;
int r=tree[id].r;
int mid=(r+l)/2;
if(mid<x) update(id*2+1,x,y,n);
else if(y<=mid) update(id*2,x,y,n);
else {update(id*2,x,mid,n);
update(id*2+1,mid+1,y,n);
}
tree[id].sum=tree[id*2].sum+tree[id*2+1].sum;//看上面解释,这一步很重要
}
ll query(int id,int x,int y){
if(x<=tree[id].l&&tree[id].r<=y){return tree[id].sum;}
//刚开始写成x>=tree[id].l.......吐血。
push_down(id);
int l=tree[id].l;
int r=tree[id].r;
int mid=(l+r)/2;
if(y<=mid){return query(id*2,x,y);}
else if(x>mid){return query(id*2+1,x,y);}
else{
return query(id*2,x,mid)+query(id*2+1,mid+1,y);//与第一个模型基本类似
}
}
③这个模型也是用来求区间和值的,但是每次更新的是给定区间的所有点的值,
所以计算和值的时候需要先判断该区间的和值是否相等,再乘以区间的大小,引入标记。
struct node
{
int l,r;
bool sta;
int val;//没有sum,后面是直接return val*区间长度
bool flag;
}tree[MAXN * 4];
int L;
void build(int id,int l,int r)
{
tree[id].l = l;
tree[id].r = r;
tree[id].val = 1;
tree[id].flag = false;//flag表示要不要对它进行操作,刚开始不用所以为false;
tree[id].sta = true;//刚开始每个区间都相同,所以是true;
if (l == r) return;//与之前l==r才进行操作不同,这里是要给所有的树标,所以当无法再分时return;
int mid = (l + r) / 2;
build(id * 2,l,mid);
build(id * 2 + 1,mid + 1,r);
}
void push_down(int id)//只需要满足一个条件
{
if (tree[id].flag)//需要对该树进行操作(更新)
{
tree[id * 2].val = tree[id * 2 + 1].val = tree[id].val;
tree[id * 2].flag = true;//需要对子树继续操作(更新)
tree[id * 2 + 1].flag = true;
tree[id].flag = false;//父树已操作结束
tree[id].sta = true;
tree[id * 2].sta = true;
tree[id * 2 + 1].sta = true;//既然要对id父树进行操作,那就说明该区间的值都要换成一个特定值,所以都相同,为true;
}
}
void push_up(int id)//需要满足两个条件↓
{
if (tree[id * 2].sta == true && tree[id * 2 + 1].sta == true)//当子树区间值都相同时,父树单值对应要更新为相同值;
{
if (tree[id * 2].val == tree[id * 2 + 1].val)//再次判断,防止刚开始的默认情况误更新
{
tree[id].val = tree[id * 2 + 1].val;
tree[id].sta = true;//父区间也为相同true;
}
else tree[id].sta = false;//不满足条件false;
}
else tree[id].sta = false;
}
void update(int id,int l,int r,int val)//特别要注意的是需要先push_down再push_up
{
if (tree[id].l >= l && tree[id].r <= r)//要操作的区间大于等于该树的区间
{
tree[id].flag = true;//要对该树进行操作
tree[id].val = val;
tree[id].sta = true;
return;
}
push_down(id);//从父到子更新;
int mid = (tree[id].l + tree[id].r) / 2;
if (l > mid) update(id * 2 + 1,l,r,val);
else if (r <= mid) update(id * 2,l,r,val);
else
{
update(id * 2,l,mid,val);
update(id * 2 + 1,mid + 1,r,val);
}
push_up(id);//从子到父再更新,确保无遗漏,或者说刚开始的push_down(id)只是对相邻的子树进行更新,然后一直dfs二分到最后,
//所以最后一定要从末尾到前面再更新一次(有些父树没更新)。
}
int query(int id,int l,int r)
{
if (tree[id].l >= l && tree[id].r <= r)
{
if (tree[id].sta == true)
{
// printf("%d %d %d\n",tree[id].l,tree[id].r,tree[id].val);
return tree[id].val * (tree[id].r - tree[id].l + 1);
}
}
push_down(id); //看需不需要对id父树进行操作,也就是说可能有一些子树还没更新到,确保再二分时已经更新完毕。
int mid = (tree[id].l + tree[id].r) / 2;
if (l > mid) return query(id * 2 + 1,l,r);
else if (r <= mid) return query(id * 2,l,r);
else
{
return query(id * 2,l,mid) + query(id * 2 + 1,mid + 1,r);
}
}