AT_abc467_g Many Sweets Problem 题解 & 整体二分学习笔记

树套树做法太慢了,这里给出一种整体二分的做法。

因为带修,所以最好不要排序。我们尝试对每个询问在线地进行二分:我们二分最大的阈值 \(mid\),使得 \([l_i,r_i]\) 中所有不小于 \(mid\) 的数之和不小于 \(k\)。一次二分是 \(\mathcal{O}(m \log V)\) 的。

我们都知道二分是一种分治,我们可以将它看成在线段树上遍历一条由根到叶子的路径。如果直接在线进行二分的话,有很多点会被重复遍历。一个点被计算的这好几次,其实可以被合并到一次进行计算的。

具体地,我们将每一个值的出现和消失看作单点 \(+x\) 和单点 \(-x\),原因见此处。于是我们便可以把这些操作和询问放到一起然后跑整体二分。

我们递归地解决这个问题:\(solve(l,r,E)\) 负责答案在 \([l,r]\) 内的询问,以及绝对值值域在 \([l,r]\) 中的操作。这些询问和操作都存储在 \(E\) 里面。

我们依次遍历这些事件,并使用一个轻量的 BIT 维护目前序列中所有处于 \((mid,r]\) 中的数的和

  • 如果我们遇到修改操作,若它的绝对值是在 \((mid,r]\) 中,那么我们在 BIT 上进行对应的操作,把它放到 \(E_r\) 里;否则直接放入 \(E_l\)

  • 遇到查询,我们直接查询该区间和 \(S\)\(S\) 的意义上面已经说了。如果 \(S \ge k_i\),那么说明对于这个询问,答案在 \((mid,r]\) 之间,放入 \(E_r\);否则,答案在 \([l,mid]\) 之间。因为我们不会把 \((mid,r]\) 里的操作放入 \(E_l\),因此,我们先在 \(k_i\) 上把这些操作的贡献减去,即 \(k_i \leftarrow k_i - S\),然后把该操作放入 \(E_l\)

接着递归 \(solve(l,mid,E_l)\)\(solve(mid+1,r,E_r)\)

对于最终答案的统计,我们另开一个 BIT 维护目前有多少个 \((mid,r]\) 的数,在遍历的顺便统计就好了。

结束条件就是 \(l = r\),无解的处理就是 \(l = r = 0\)

我们发现,树套树的两个维度分别对应了权值和区间,整体二分,其实是不像树套树对第二维(区间)进行维护,而是直接实时计算,把所有调用通过一次计算搞定。空间少了,码量小了,常数小了,但是失去了在线功能。由于每个操作在每一层被处理了一次,时间为 \(\log n\),因此总复杂度是 \(\mathcal{O}(m \log V \log n)\)

// Problem: G - Many Sweets Problem
// Contest: AtCoder - AtCoder Beginner Contest 467
// Memory Limit: 1024 MB
// Time Limit: 3000 ms

#include <bits/stdc++.h>
#define int long long
#define Misaka namespace
#define Network std
using Misaka Network;

/*
if(s[i].id == 31) 
if(l == 0 && r == 250000000) 
*/

const int N = 1e5 + 7;
int n, q, a[N], m;
struct Event{
	int opt, id, ans;
	int c, x;
	int l, r, k;
} s[N << 2], t[N << 2];
int to[N << 2];

struct BIT{
	int val[N];
	#define lb(x) (x & (-x))
	stack<tuple<int, int>> q;
	void add(int x, int k){
		for(int i = x; i <= n; i += lb(i)) val[i] += k;
		q.push({x, k});
	}
	int qur(int x){
		int res = 0;
		for(int i = x; i; i -= lb(i)) res += val[i];
		return res;
	}
	int sum(int l, int r){
		return qur(r) - qur(l - 1);
	}
	void init(){
		while(q.size()){
			auto [x, vv] = q.top(); q.pop();
			for(int i = x; i <= n; i += lb(i)) val[i] -= vv;
		}
	}
} bit, cnt;

void solve(int l, int r, int ql, int qr){
	
	if(l == r){
		for(int i = ql; i <= qr; i ++){
			if(l == 0) s[i].ans = -1;
			else s[i].ans += s[i].k / l + (s[i].k % l > 0);
		}
		return;
	}
	
	int mid = (l + r) >> 1;
	
	bit.init();
	cnt.init();
	
	int cl = 0, cr = 0;
	for(int i = ql; i <= qr; i ++){
		if(s[i].opt == 1){
			if(abs(s[i].x) > mid){
				bit.add(s[i].c, s[i].x), cr ++, to[i] = 2;
				cnt.add(s[i].c, s[i].x / abs(s[i].x));
			}
			else cl ++, to[i] = 1;
		}
		else{
			int S = bit.sum(s[i].l, s[i].r);
			if(S >= s[i].k) cr ++, to[i] = 2;
			else cl ++, to[i] = 1, s[i].k -= S, s[i].ans += cnt.sum(s[i].l, s[i].r);
		}
		
	}
	
	int u = ql, v = ql + cl;
	for(int i = ql; i <= qr; i ++){
		if(to[i] == 1) t[u ++] = s[i];
		else t[v ++] = s[i];
	}
	for(int i = ql; i <= qr; i ++) s[i] = t[i];
	
	if(cl) solve(l, mid, ql, ql + cl - 1);
	if(cr) solve(mid + 1, r, ql + cl, qr);
	
}

signed main(){
	ios::sync_with_stdio(0), cin.tie(0);
	
	cin >> n >> q;
	for(int i = 1; i <= n; i ++){
		cin >> a[i]; m ++;
		s[m] = {1, m, 0, i, a[i], 0, 0, 0};
	}
	for(int i = 1; i <= q; i ++){
		int c, x, l, r, k;
		cin >> c >> x >> l >> r >> k;
		m ++; s[m] = {1, m, 0, c, -a[c], 0, 0, 0};
		a[c] = x;
		m ++; s[m] = {1, m, 0, c, a[c], 0, 0, 0};
		m ++; s[m] = {2, m, 0, 0, 0, l, r, k};
	}
	
	solve(0, 1e9, 1, m);
	sort(s + 1, s + m + 1, [](Event a, Event b){
		return a.id < b.id;
	});
	for(int i = 1; i <= m; i ++){
		if(s[i].opt == 2) cout << s[i].ans << "\n";
	}
	
	return 0;
}
posted @ 2026-07-20 14:18  Trent900  阅读(12)  评论(0)    收藏  举报