Info operator + (const Info& a, const Info& b) {return {a.sum + b.sum, a.cnt + b.cnt};}
voidsolve() { int n, Q; std::cin >> n >> Q; std::vector<int> a(n); for (auto& ai : a) {std::cin >> ai;} SegmentTree<Info> T(n + Q); std::vector<int> positives; for (auto& ai : a) if (ai > 0) {positives.push_back(ai);} std::vector<std::pair<int, int>> querires(Q); for (auto&[x, v] : querires) { std::cin >> x >> v; --x; if (v > 0) {positives.push_back(v);} }
Discreter discreter(positives);
std::map<int, int> cnt; for (auto& ai : a) if (ai > 0) {cnt[ai] += 1;} for (auto&[ai, c] : cnt) {T.modify(discreter.query(ai), {c * ai, c});}
i64 negative_abs_sum{}; for (auto& ai : a) if (ai <= 0) {negative_abs_sum += -ai;}
for (auto&[x, v] : querires) { if (a[x] <= 0) {negative_abs_sum -= std::abs(a[x]);} else { int id{discreter.query(a[x])}; T.modify(id, {-a[x], -1});//很显然 } if (v <= 0) { negative_abs_sum += std::abs(v); } else { int id{discreter.query(v)}; T.modify(id, {v, 1});//很显然 } int k{n + Q}; {//第一遍k int lo{}, hi{n + Q - 1}; while (lo <= hi) { int mid{(lo + hi) / 2}; (T.rangeQuery(0, mid + 1).sum > negative_abs_sum) ? hi = mid - 1, k = mid : lo = mid + 1; } }
a[x] = v;
if (k == n + Q) { std::cout << T.rangeQuery(0, n + Q).cnt + 1 << "\n"; continue ; }
i64 pre_sum{T.rangeQuery(0, k).sum};
int k_take{}, pre_take{T.rangeQuery(0, k).cnt};//直接维护出来了 [0, k - 1] 的所有正数个数
{//第二遍找k取了多少个 auto k_sum{T.rangeQuery(k, k + 1).sum}; auto k_val{discreter.queryInv(k)}; //i64 lo{1}, hi{k_sum / k_val}; while (lo <= hi) { // i64 mid{(lo + hi) / 2}; // (pre_sum + mid * k_val > negative_abs_sum) ? // hi = mid - 1, k_take = mid : lo = mid + 1; //} k_take = (negative_abs_sum - pre_sum + k_val - 1) / k_val; }
template<class F> intfindFirst(int p, int l, int r, int x, int y, F &&pred){ if (l >= y || r <= x) { return-1; } if (l >= x && r <= y && !pred(info[p])) { return-1; } if (r - l == 1) { return l; } int m = (l + r) / 2; int res = findFirst(2 * p, l, m, x, y, pred); if (res == -1) { res = findFirst(2 * p + 1, m, r, x, y, pred); } return res; } template<class F> intfindFirst(int l, int r, F &&pred){ returnfindFirst(1, 0, n, l, r, pred); }
考虑怎么优化掉二分这个 log,可以在线段树上二分。
先解决一个类似且简单的线段树二分问题:
对于 (a_1…a_n),求第一个 (a_i > k) 的 (i),带修
显然维护一个最大值的位置线段树:不优化的做法就是二分右端点,(check) T.rangeQuery(0, mid + 1).mx > k
intfindFirst(int p, int l, int r, int x, int y, i64 tar){ if (l >= y || r <= x) { return-1; } if (l >= x && r <= y && (info[p].sum <= tar)) { return-1; } if (r - l == 1) { return l; } int m = (l + r) / 2; int res = findFirst(2 * p, l, m, x, y, tar); if (res == -1) { res = findFirst(2 * p + 1, m, r, x, y, tar - info[2 * p].sum);//把左边的所有贡献加起来,因为是求前缀和,也就是说接下来只要查找到比 tar 减去左边的贡献后还大的值就行了。 } return res; } //第一遍线段树二分,找到第一个大于负数绝对值的前缀和的下标 int k{T.findFirst(0, n + Q, negative_abs_sum)}; if (k == -1) {std::cout << T.rangeQuery(0, n + Q).cnt + 1 << "\n"; continue;} //第二遍找k取了多少个 auto pre_sum{T.rangeQuery(0, k).sum}, pre_take{T.rangeQuery(0, k).cnt}; int k_val{discreter.queryInv(k)}; i64 k_take{(negative_abs_sum - pre_sum) / k_val + 1};