原理:

线段树比树状数组要灵活许多,只要能满足“区间加法”的题,都能用线段树来做。但是对于没有修改的情况,区间和可以用树状数组,Max/Min可以用ST表。

线段树的本质是做区间分解,各个子区间的Sum/Max/Min合成大区间的,例如【2,12】=【2】+【3,4】+【5,7】+【8,10】+【11,12】.

线段树总结篇

可以证明的是,当n>=3时,[1, n]的任意子区间[l, r] 分解的子区间不超过 2log(n-1)

这样的话,不管是查询还是修改,只需要查询/修改 log(len) 次,而不用 len 次(len是区间长度)。

实现:

建树:可以不用单点修改n次,可以递归到叶子节点,再赋值

单点修改:找到那个点,改掉就可以了

区间修改:不能递归到叶子节点,而是遇到包含在修改范围内的子区间就打上Lazy标记。Update有两种,一种是Add,是个改变量,另一种是Set,是设置为,两者在PushDownh和区间赋值时略有不同

应用:
1. leetcode1526. 形成目标数组的子数组最少增加次数:

  dfs(i, j) ,再在线段树上查询[i, j]的最小值

2. leetcode1157. 子数组中占绝大多数的元素:

  线段树维护绝对众数,只是区间合并时与普通的不同,采用抵消法

3. leetcode699. 掉落的方块:

  维护区间的最大值,由于数值范围过大,需要将所有出现的点进行离散化

4. leetcode1521. 找到最接近目标值的函数值:

  二分时,查询[right, mid]的区间与

5. leetcdeo715. Range 模块

  区间修改+动态开点,由于数值范围很大,但是又不能离散化(离散化只保留相对顺序,而这里必须修改那么长的区间)

总的来说,就是,要么直接给出左右端点,要么就是dfs/二分/滑动窗口等得到的左右端点,要么范围太大进行离散化/动态开点。

模板:

1. 区间修改(Add)+最小值

    struct SegTree {
        #define maxn  100010 //元素总个数
        #define ls l,m,rt<<1
        #define rs m+1,r,rt<<1|1
        #define INF 0x3f3f3f3f
        long long Sum[maxn<<2],Add[maxn<<2], Max[maxn<<2], Min[maxn<<2];//Sum求和,Add为懒惰标记, Max区间最大值 
        // int A[maxn],n;//存原数组数据下标[1,n] 
        vector<int>A;

        void init(vector<int>& _A){
            A = _A;
            for(int i = 1;i < maxn;i++)  Min[i] = INF;
        }

        //PushUp函数更新节点信息 ,这里是求和
        void PushUp(int rt){
            Sum[rt]=Sum[rt<<1]+Sum[rt<<1|1];
            Max[rt] = max(Max[rt<<1], Max[rt<<1|1]); // 标记不需要向上维护x
            Min[rt] = min(Min[rt<<1], Min[rt<<1|1]);  // 
        }
        //Build函数建树 
        void Build(int l,int r,int rt){ //l,r表示当前节点区间,rt表示当前节点编号
            // cout << "build: " << l << " " << r << endl;
            if(l==r) {//若到达叶节点 
                Sum[rt]=A[l-1];//储存数组值 
                Max[rt]=Min[rt]=A[l-1];
                return;
            }
            int m=(l+r)>>1;
            //左右递归 
            Build(l,m,rt<<1);
            Build(m+1,r,rt<<1|1);
            //更新信息 
            PushUp(rt);
        }

        void Update(int L,int R,int C,int l,int r,int rt){//L,R表示操作区间,l,r表示当前节点区间,rt表示当前节点编号 
            // cout << "Update: " << l << " " << r << endl;
            if(L <= l && r <= R){//如果本区间完全在操作区间[L,R]以内 
                Sum[rt] +=C*(r-l+1);//更新数字和,向上保持正确
                Add[rt] +=C;//增加Add标记,表示本区间的Sum正确,子区间的Sum仍需要根据Add的值来调整
                Max[rt] = Max[rt]+C;
                Min[rt] = Min[rt]+C;
                return ; 
            }
            int m=(l+r)>>1;
            PushDown(rt,m-l+1,r-m);//下推标记
            //这里判断左右子树跟[L,R]有无交集,有交集才递归 
            if(L <= m) Update(L,R,C,l,m,rt<<1);
            if(R >  m) Update(L,R,C,m+1,r,rt<<1|1); 
            PushUp(rt);//更新本节点信息 
        } 

        void PushDown(int rt,int ln,int rn){
            //ln,rn为左子树,右子树的数字数量。 
            // cout << "rt: " << rt << endl;
            if(Add[rt]){
                //下推标记 
                Add[rt<<1]+=Add[rt];
                Add[rt<<1|1]+=Add[rt];
                //修改子节点的Sum使之与对应的Add相对应 
                Sum[rt<<1]+=Add[rt]*ln;
                Sum[rt<<1|1]+=Add[rt]*rn;

                Max[rt<<1] = Max[rt<<1]+Add[rt];
                Max[rt<<1|1] = Max[rt<<1|1]+Add[rt];
                Min[rt<<1] = Min[rt<<1]+Add[rt];
                Min[rt<<1|1] = Min[rt<<1|1]+Add[rt];

                //清除本节点标记 
                Add[rt]=0;
            }
        }

        int Query(int L,int R,int l,int r,int rt){//L,R表示操作区间,l,r表示当前节点区间,rt表示当前节点编号
            if(L <= l && r <= R){
                //在区间内,直接返回 
                return Min[rt];
            }
            int m=(l+r)>>1;
            //下推标记,否则Sum可能不正确
            PushDown(rt,m-l+1,r-m); 
            
            //累计答案
            int ANS=INF;
            if(L <= m) ANS=min(ANS, Query(L,R,l,m,rt<<1));
            if(R >  m) ANS=min(ANS, Query(L,R,m+1,r,rt<<1|1));
            return ANS;
        } 
    }segTree; 
View Code

相关文章:

  • 2021-10-11
  • 2021-11-10
猜你喜欢
  • 2021-04-21
  • 2021-05-31
相关资源
相似解决方案