Problem: 2569. 更新数组后处理求和查询
思路
设计对数组的一个部分进行统一修改,必然是线段树,不然一次修改的耗时为O(n)
解题方法
先构建一个线段树,然后每次修改进行懒标记即可
Code
class Solution {
static constexpr int MX = 4e5 + 1;
int cnt1[MX]; // 统计1的个数
bool flip[MX]; // 懒标记, 标记是否需要反转
// 维护区间中1的个数
void maintain(int o) {
// 1的个数等于其两个子区间的1的个数之和
cnt1[o] = cnt1[o * 2] + cnt1[o * 2 + 1];
}
// 区间反转 对第o个特殊区间,即[l, r] 进行反转
void do_(int o, int l, int r) {
// 1的个数为 区间长度 - 原有的1的个数
cnt1[o] = r - l + 1 - cnt1[o];
// flip用于标记整个区间是否需要反转(懒标记)
flip[o] = !flip[o];
}
// 构造特殊区间
void build(vector<int> &a, int o, int l, int r) {
if (l == r) {
cnt1[o] = a[l - 1];
return;
}
int m = (l + r) / 2;
build(a, o * 2, l, m); // 耗时log(n)
build(a, o * 2 + 1, m + 1, r); // 耗时log(n)
maintain(o);
}
// 反转区间[L, R], 目前的特殊区间为[l, r]
void update(int o, int l, int r, int L, int R) {
if (L <= l && r <= R) {
// 直接update整个区间 耗时log(n)
do_(o, l, r);
return;
}
// 取[l, r]中点
int m = (l + r) / 2;
// 如果标记了需要反转
if (flip[o]) {
// 执行区间反转,但只向下一层 耗时o(1)
do_(o * 2, l, m);
do_(o * 2 + 1, m + 1, r);
// 该层的懒标记消除
flip[o] = false;
}
// 如果中点在L右侧,那么对[l, m]的特殊区间进行更新 耗时log(n)
// 如果中点在R左侧,那么对[m + 1, R]的特殊区间进行更新 耗时log(n)
if (m >= L) update(o * 2, l, m, L, R);
if (m < R) update(o * 2 + 1, m + 1, r, L, R);
// 重新统计1的个数,耗时log(n)
maintain(o);
}
public:
vector<long long> handleQuery(vector<int>& nums1, vector<int>& nums2, vector<vector<int>>& queries) {
int n = nums1.size();
build(nums1, 1, 1, n);
vector<long long> ret;
long long sum = accumulate(nums2.begin(), nums2.end(), 0LL);
for (auto &q : queries) {
if (q[0] == 1) update(1, 1, n, q[1] + 1, q[2] + 1);
else if(q[0] == 2) sum += 1LL * q[1] * cnt1[1];
else ret.push_back(sum);
}
return ret;
}
};