线段树及动态开点

题目传送门
什么情况下使用线段树?
1.区间修改,区间查询问题
2.问题满足加法性质,这里的加法性质指一个大区间的答案可以通过其分割后的两个子区间的答案合并获得,例如最大值,最小值,区间和。以及本题中的绝对众数(区间老大)。但是像如区间众数、区间最长连续、最长不下降问题就不满足加法性质,也就不能用线段树。
这里分析一下为何绝对众数满足加法性质:
遇到绝对众数,就想摩尔投票,本质就是从一个个子区间中选出老大,老大可能这个区间中名副其实的绝对众数,也可能不是,所以最终得到答案后需要判断一下个数(二分)。


class MajorityChecker {
    struct node{
        int l,r;
        int x,cnt;
        node(){
            
        }//这里一定要有一个默认构造函数,因为在运行前已经开辟了tree[4n]数组,每一个对象已经构造好了,但是我们提供的是带参数的构造函数,必须还要提供一个缺省参数的构造函数。
        node(int a,int b,int c,int d){
            l = a;
            r = b;
            x = c;
            cnt = d;
        }
        const node operator+(const node& tmp)const{
            int left = l,right = tmp.r;
            if(x == tmp.x){
                return node(left,right,x,cnt+tmp.cnt);
            }else if(cnt<tmp.cnt){
                return node(left,right,tmp.x,tmp.cnt-cnt);
            }else{
                return node(left,right,x,cnt-tmp.cnt);
            }
        }
    }tree[68004];
    vector<int>nums;
    int n,val;
    
    void build(int k,int left,int right){
        tree[k].l = left;
        tree[k].r = right;
        if(tree[k].l == tree[k].r){
            tree[k].x = nums[left-1];
            tree[k].cnt = 1;
            return;
        }
        int mid = (left+right)>>1;
        build(k<<1,left,mid);
        build(k<<1|1,mid+1,right);
        tree[k] = tree[k<<1]+tree[k<<1|1];
        //cout<<tree[k].l<<" "<<tree[k].r<<" "<<tree[k].x<<" "<<tree[k].cnt<<endl;
    }
    node query1(int k,int left,int right){
        if(tree[k].l >= left && tree[k].r <= right){
            return tree[k];
        }
        node ret(0,0,nums[left-1],0);
        int mid = (tree[k].l+tree[k].r)>>1;
        if(mid>=left){
            ret = ret+query1(k<<1,left,right);
        }
        if(mid<right){
            ret = ret+query1(k<<1|1,left,right);
        }
        return ret;
    }
    vector<vector<int>>pos = vector<vector<int>>(20005);
public:
    MajorityChecker(vector<int>& arr) {
        nums = arr;
        n = arr.size();
        val = arr[0];
        build(1,1,n);
        for(int i = 0;i<n;i++){
            pos[arr[i]].emplace_back(i);
        }
    }
    bool func(int val,int threshold,int left,int right){
        int a = lower_bound(pos[val].begin(),pos[val].end(),left)-pos[val].begin();
        int b = upper_bound(pos[val].begin(),pos[val].end(),right)-pos[val].begin();
        int len = b-a;
        return len>=threshold;
    }
    int query(int left, int right, int threshold) {
        node t = query1(1,left+1,right+1);
        //cout<<t.x<<endl;
        if(func(t.x,threshold,left,right)){
            return t.x;
        }else{
            return -1;
        }
    }
};

/**
 * Your MajorityChecker object will be instantiated and called as such:
 * MajorityChecker* obj = new MajorityChecker(arr);
 * int param_1 = obj->query(left,right,threshold);
 */

线段树易错点:
1、下标
2、lazyTag操作时,注意修改自身节点lazy置零。
空间优化操作:
一般来说,我们开辟4N的大小,但是还可以更优,以本题20000来说,最小的满足2的x次方大于等于20000的数字为32768,那么直接乘以2就是我们需要开辟的空间,即65536。

动态开点

动态开点的思想为以值val作为下标,当值的范围在10^9左右时,可以不用使用build()函数,仅在单点插入的时候新建点,此时最好使用vector动态扩容,更新vector的大小cnt。这样比直接创建一个数组所消耗的时间更少。
适用场景:
val范围较大,可为负数。
索引下标可从0开始。
易错点:
1、数值溢出,mid,left,right均可能爆int
2、下标注意,可以从1(根节点下标为0)开始或从2(原始线段树)开始
3、查询操作,当k为-1,表示该区间内之前未插入节点,可直接返回查询结果,不用继续开点。
4、尤其需要注意的是,如果query不给点的l和r赋值,那么必须初始化tree[0]的l和r,否则在查询时会出错。
模板代码:

class Solution {
    typedef long long ll;
    const ll cha = 3e9+5;
    const ll xmin = -3e9,xmax = 3e9;
    struct node{
        ll l,r,mx = 0;
        int l_son = -1,r_son = -1;
    };
    vector<node>tree = vector<node>(1,node());
    int n,cnt = 1;
    ll t;
    void pushup(int k){
        int left_son = tree[k].l_son;
        int right_son = tree[k].r_son;
        if(left_son != -1){
            tree[k].mx = max(tree[k].mx,tree[left_son].mx);
        }
        if(right_son != -1){
             tree[k].mx = max(tree[k].mx,tree[right_son].mx);
        }
        
    }
    ll query(int k,ll l,ll r,ll left,ll right){
        if(k == -1){
            return 0;
        }
        tree[k].l = l;//这里两行确实可以不做,但是必须要初始化tree[0]的l和r.
        tree[k].r = r;
        if(l >= left && r <= right){
            return tree[k].mx;
        }
        ll mid = (tree[k].l+tree[k].r)>>1;
        ll ans = 0;
        if(mid >= left){
            ans = max(ans,query(tree[k].l_son,l,mid,left,right));
        }
        if(right > mid){
            ans = max(ans,query(tree[k].r_son,mid+1,r,left,right));
        }
        return ans;
    }
    void update(int k,int id,int val){
        if(tree[k].l == tree[k].r){
            tree[k].mx = val;
        }else{
            int mid = (tree[k].l+tree[k].r)>>1;
            if(mid >= id){
                if(tree[k].l_son == -1){
                    tree.emplace_back(node());
                    tree[cnt].l = tree[k].l;
                    tree[cnt].r = mid;
                    tree[k].l_son = cnt++;
                }
                update(tree[k].l_son,id,val);
            }else{
                if(tree[k].r_son == -1){
                    tree.emplace_back(node());
                    tree[cnt].l = mid+1;
                    tree[cnt].r = tree[k].r;
                    tree[k].r_son = cnt++;
                }
                update(tree[k].r_son,id,val);
            }
            pushup(k);
        }
    }
    int dfs(vector<int>& nums,int id){
        if(id != 0){
            dfs(nums,id-1);
        }
        int val = query(0,xmin,xmax,1L*nums[id]-t,1L*nums[id]+t);
        if(val != 0 || id == 0)
            update(0,1L*nums[id],val+1);
        return val == 0 ? -1 :val;
    }
public:
    int maximumJumps(vector<int>& nums, int target) {
        n = nums.size();
        t = target;
        return dfs(nums,n-1);
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值