LeetCode每日一题之3165. 不包含相邻元素的子序列的最大和

解题思路

对于queries[i] 每次操作会将nums[posi]的值修改为xi,对于nums[posi]有以下两种情况(下面把posi看做第i个位置, 数组长度为n)

  • 选nums[i]:那么相邻的位置i-1 和 i+1均不能被选择,左右两侧变成nums[0 到 i - 2] 和 nums[i + 2 到 n-1]
  • 不选nums[i]: 那么相邻的位置i-1 和 i+1 没有任何限制,左右两侧变成nums[0 到 i - 1] 和 nums[i + 1, n - 1]

因为要频繁的修改查询所以要用线段树。对于每个结点t[l,r],表示区间为[l,r]。对于nums[l] 到 nums[r]这个子数组,假设区间t[l,r],其中x,y取值为0或1;x 表示左边界的选择情况;y 表示右边界的选择情况;0 表示对应的边界元素一定没有被选择;1 表示对应的边界元素可能被选择,也就是没有任何要求。

如果(l = r) 即单个值。那么x = 0 或者y = 0时,即 l 或 r 一定有一个没有被选择,因为只有一个元素,所以就是没选择任何元素即值=0。那么x = 1 并且 y = 1时就是这个元素可选可不选。没有任何要求,所有值为 max{nums[l], 0}。

如果(l != r) 即一个区间,那么有以下四种情况,以下t[l, r]代表区间[l, r]的最大值,区间是闭区间,m = [l + r] / 2

  • 对于t[l, r] (l = 0, r = 0):
    它表示左右边界l 与 r一定没有被选择,在线段树中该区间[l, r],由左子区间[l, m] 和 右子区间[m + 1, r]组成。

    • 当左子区间是(0, 0)时,右子区间一定要满足右边界元素一定没有被选择。因为右子区间左边界没有限制,同时为了使结果尽可能大所以右侧是(1, 0),此时的值为l[0, 0] + r[1, 0];
    • 当左侧区间是(0, 1)时,右子区间一定要满足右边界元素一定没有被选择,因为右子区间左边界有限制(因为左子区间右边界m=1)所以右侧是(0, 0),此时的值为l[0, 1] + r[0, 0]; 所以:t[l,r] = max(l[0, 0] + r[1, 0], l[0, 1] + r[0, 0])
  • 对于t[l,r] (l = 0, r = 1):
    它表示左右边界 l一定没有被选择 与 r没有限制(可选可不选),在线段树中该区间[l, r],由左子区间[l, m] 和 右子区间[m + 1, r]组成。

    • 当左子区间是(0, 0)时,右子区间一定要满足右边界元素没有限制(即r=1)。因为右子区间左边界没有限制,同时为了使结果尽可能大所以右侧是(1, 1),此时的值为l[0, 0] + r[1, 1];
    • 当左侧区间是(0, 1)时,右子区间一定要满足右边界元素没有限制(即r=1),因为右子区间左边界有限制(因为左子区间右边界m=1)所以右侧是(0, 1),此时的值为l[0, 1] + r[0, 1]; 所以:t[l,r] = max(l[0, 0] + r[1, 1], l[0, 1] + r[0, 1])
  • 对于t[l, r] (l = 1, r = 0)
    t[l,r] = max(l[1, 0] + r[1, 0], l[1, 1] + r[0, 0])

  • 对于t[l, r] (l = 1, r = 1)
    t[l,r] = max(l[1, 0] + r[1, 1], l[1, 1] + r[0, 1])

所以:

  • t[l,r] (0,0)=max{L(0,0)+R(1,0),L(0,1)+R(0,0)}
  • t[l,r] (0,1)=max{L(0,0)+R(1,1),L(0,1)+R(0,1)}
  • t[l,r] (1,0)=max{L(1,0)+R(1,0),L(1,1)+R(0,0)}
  • t[l,r] (1,1)=max{L(1,0)+R(1,1),L(1,1)+R(0,1)}

示例代码

//每个点有4种状态
struct SegNode {
    SegNode() {
        v00 = v01 = v10 = v11 = 0;
    }
    void set(long long v) {
        v00 = v01 = v10 = 0;
        v11 = max(v, 0LL); //单个元素最大值
    }
    long long best() {
        return v11; //把整个区间都考虑进去才最大
    }
    
    long long v00, v01, v10, v11;
};

//线段树类
class SegTree {
public:
    SegTree(int n): n(n), tree(n << 2 | 1) {}
    void init(const vector<int>& nums) {
        internal_init(nums, 1, 1, n);
    }
    void update(int x, int v) {
        internal_update(1, 1, n, x + 1, v);
    }
    //修个一个值后整个区间的最大值
    long long query() {
        return tree[1].best();
    }

private:
    //初始化线段树
    //参数1:数组   参数2;第x端区间
    //参数3、4:当前区间
    void internal_init(const vector<int>& nums, int x, int l, int r) {
        if (l == r) {
            //nums的下标是从0开始的 线段树是[1到n]闭区间
            tree[x].set(nums[l - 1]);
            return;
        }
        int mid = (l + r) >> 1;
         //左右孩子区间初始化
        internal_init(nums, x << 1, l, mid);
        internal_init(nums, x << 1 | 1, mid + 1, r);
        pushup(x); //更新父
    }
     //更新结点值
    //参数1:当前为第x区间
    //参数2,3:当前区间范围
    //参数4:更新位置下标
    //参数5:更新的值
    void internal_update(int x, int l, int r, int pos, int v) {
        //pos没在[l, r]中
        if (l > pos || r < pos) {
            return;
        }
        if (l == r) {
            tree[x].set(v);
            return;
        }
        //在左右儿子区间寻找pos位置
        int mid = (l + r) >> 1;
        internal_update(x << 1, l, mid, pos, v);
        internal_update(x << 1 | 1, mid + 1, r, pos, v);
        pushup(x);//更新父
    }
    //向上更新父节点区间的值
    void pushup(int x) {
        int l = x * 2, r = x * 2 + 1; //左右孩子区间下标
        tree[x].v00 = max(tree[l].v00 + tree[r].v10, tree[l].v01 + tree[r].v00);
        tree[x].v01 = max(tree[l].v00 + tree[r].v11, tree[l].v01 + tree[r].v01);
        tree[x].v10 = max(tree[l].v10 + tree[r].v10, tree[l].v11 + tree[r].v00);
        tree[x].v11 = max(tree[l].v10 + tree[r].v11, tree[l].v11 + tree[r].v01);
    }

private: 
    int n; //值的个数 即叶子结点个数
    vector<SegNode> tree;
};

class Solution {
public:
    int maximumSumSubsequence(vector<int>& nums, vector<vector<int>>& queries) {
        int n = nums.size();
        SegTree tree(n);
        tree.init(nums);
        
        int ans = 0;
        for (const auto& q: queries) {
            tree.update(q[0], q[1]);  //修改值
            ans = ((long long)ans + tree.query()) % mod;
        }
        return ans;
    }

private:
    static constexpr int mod = 1000000007;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值