解题思路
对于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;
};