题目描述
给你一个整数数组 nums
和一个二维数组 queries
,其中 queries[i] = [posi, xi]
。
对于每个查询 i
,首先将 nums[posi]
设置为 xi
,然后计算查询 i
的答案,该答案为 nums
中 不包含相邻元素 的
子序列
的 最大 和。
返回所有查询的答案之和。
由于最终答案可能非常大,返回其对 109 + 7
取余 的结果。
子序列 是指从另一个数组中删除一些或不删除元素而不改变剩余元素顺序得到的数组。
示例 1:
输入:nums = [3,5,9], queries = [[1,-2],[0,-3]]
输出:21
解释:
执行第 1 个查询后,nums = [3,-2,9]
,不包含相邻元素的子序列的最大和为 3 + 9 = 12
。
执行第 2 个查询后,nums = [-3,-2,9]
,不包含相邻元素的子序列的最大和为 9 。
示例 2:
输入:nums = [0,-1], queries = [[0,-5]]
输出:0
解释:
执行第 1 个查询后,nums = [-5,-1]
,不包含相邻元素的子序列的最大和为 0(选择空子序列)。
解题思路
本题是可以修改数组元素值的 198. 打家劫舍。
为了解决本题,首先来换一个角度,用分治的思想解决打家劫舍。
设 f(A) 为数组 A 的打家劫舍的答案。把 nums 从中间切开,分成左右两个子数组,分别记作 a 和 b。要计算 f(nums),看上去,我们只需要分别计算 f(a) 和 f(b)。但这是不对的,万一同时选了 a 的最后一个数和 b 的第一个数,就不满足题目要求了。怎么办?要么不选 a 的最后一个数,要么不选 b 的第一个数。
对于修改操作,我们可以用线段树的单点修改实现,线段树的每个节点维护对应区间的值。对于查询操作,由于询问的是整个数组,询问结果就是线段树根节点的 值,累加到答案中。
AC代码
class Solution {
vector<array<unsigned int, 4>> t;
void maintain(int o) {
auto& a = t[o * 2];
auto& b = t[o * 2 + 1];
t[o] = {
max(a[0] + b[2], a[1] + b[0]),
max(a[0] + b[3], a[1] + b[1]),
max(a[2] + b[2], a[3] + b[0]),
max(a[2] + b[3], a[3] + b[1]),
};
}
void build(vector<int>& nums, int o, int l, int r) {
if (l == r) {
t[o][3] = max(nums[l], 0);
return;
}
int m = (l + r) / 2;
build(nums, o * 2, l, m);
build(nums, o * 2 + 1, m + 1, r);
maintain(o);
};
void update(int o, int l, int r, int i, int val) {
if (l == r) {
t[o][3] = max(val, 0);
return;
}
int m = (l + r) / 2;
if (i <= m) {
update(o * 2, l, m, i, val);
} else {
update(o * 2 + 1, m + 1, r, i, val);
}
maintain(o);
};
public:
int maximumSumSubsequence(vector<int>& nums, vector<vector<int>>& queries) {
int n = nums.size();
t.resize(2 << (32 - __builtin_clz(n)));
build(nums, 1, 0, n - 1);
long long ans = 0;
for (auto& q : queries) {
update(1, 0, n - 1, q[0], q[1]);
ans += t[1][3];
}
return ans % 1'000'000'007;
}
};