您的位置:首页 > 编程语言 > C语言/C++

线段树3种基础模型的理解和记忆(任意区间求和,任意区间的所有数加上相同数(懒操作),任意区间所有数变成同一个值再求和)

2016-04-24 08:08 706 查看
①首先是最普通的:区间求和并更新
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 longvoid 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].lb245>= 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);}}
                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息