单侧递归线段树

\(\text{luogu-4198}\)

单侧递归线段树。

之前写过这个东西,但已经完全忘记了,训练遇到了这种题就顺便总结一下。

这题算得上是单侧递归线段树的模板题了,简要题意就是找作为前缀最大的楼房斜率的个数。

这个东西比较抽象,因为没办法容易的左右区间合并。令 \(t_p\) 表示 \([l_p,r_p]\) 这个区间的答案,会发现如果只维护这个信息,我们无法做到 pushup。问题在于,右区间可能有比左区间斜率小的,也就是说我们只关注每个区间的斜率最大值,于是我们再记录一个 \(mx_p\) 表示区间斜率最大值。

这样我们可以类似分治的找要合并的右区间中斜率大于 \(mx_{ls}\) 的个数。

这就是单侧递归线段树,时间复杂度是 \(O(n \log^2 n)\) 的。下面是核心代码:

struct Seg {
	#define ls (p << 1)
	#define rs (p << 1 | 1)
	#define mid (l + r >> 1)
	ll t[N << 2]; double mx[N << 2];
	ll qry(ll p, ll l, ll r, double x) {
		if(mx[p] <= x) return 0;
		if(l == r) return (mx[p] > x);
		if(mx[ls] <= x) return qry(rs, mid + 1, r, x);
		return t[p] - t[ls] + qry(ls, l, mid, x);
	}
	void pushup(ll p, ll l, ll r) {
		mx[p] = max(mx[ls], mx[rs]);
		t[p] = t[ls] + qry(rs, mid + 1, r, mx[ls]);
	}
	void upd(ll p, ll l, ll r, ll x, ll y) {
		if(l == r) { t[p] = 1, mx[p] = 1.0 * y / x; return; }
		if(x <= mid) upd(ls, l, mid, x, y);
		else upd(rs, mid + 1, r, x, y);
		pushup(p, l, r);
	}
	#undef ls
	#undef rs
	#undef mid
} T;

\(\text{luogu-9130}\)

今天训练的题。

这个题和 luogu-4198 的相似点在于,左区间对右区间也有影响,不能单纯合并。

考虑单侧递归线段树,首先肯定要维护答案 \(w_p\),其次影响右区间的就是溢出的干草个数 \(ot_p\)

为了能正常更新 \(ot_p\),还需要维护当前区间占用了多少个位置 \(c_p\)

\(c_p\) 最容易更新,即 \(c_p = c_{ls_p} + \min(ot_{ls_p} + c_{rs_p}, r - mid)\),左区间占用个数加上右区间占用个数。

其次 \(ot_p = ot_{rs_p} + \max(ot_{ls_p} + c_{rs_p} - (r - mid), 0)\),右区间溢出个数加上左区间可能溢出的个数。

下面就是答案的更新,\(w_p = w_{ls} + f(rs, ot_{ls})\),其中,\(f(x,y)\) 函数表示 \([l_x,r_x]\) 这个区间在接受左区间的溢出之后的答案,具体的,也是类似分治的实现:

  • 若没有溢出到当前区间的右半区间,则答案为 \(f(ls_x,y)+w_x-w_{ls_x}\)
  • 否则,答案为 \(\frac{(l+r) \cdot (r-l+1)}{2} + f(rs_x,y-(mid-l+1-c_{ls_x})+ot_{ls_x})\)

和 luogu-4198 的核心思路几乎相同。

struct Seg {
	#define mid (l + r >> 1)
	ll cnt, ls[N], rs[N], c[N], w[N], ot[N];
	ll S(ll l, ll r) { return (l + r) % MOD * ((r - l + 1) % MOD) % MOD * inv2 % MOD; }
	ll f(ll p, ll l, ll r, ll x) {
		if(!x) return w[p];
		if(l == r) return l % MOD;
		if(x + c[ls[p]] <= mid - l + 1) return (f(ls[p], l, mid, x) + w[p] - w[ls[p]] + MOD) % MOD;
		return (S(l, mid) + f(rs[p], mid + 1, r, x - (mid - l + 1 - c[ls[p]]) + ot[ls[p]])) % MOD;
	}
	void pushup(ll p, ll l, ll r) {
		c[p] = c[ls[p]] + min(ot[ls[p]] + c[rs[p]], r - mid);
		w[p] = (w[ls[p]] + f(rs[p], mid + 1, r, ot[ls[p]])) % MOD;
		ot[p] = ot[rs[p]] + max(ot[ls[p]] + c[rs[p]] - r + mid, 0ll);
	}
	void upd(ll &p, ll l, ll r, ll x, ll y) {
		if(!p) p = (++ cnt);
		if(l == r) {
			if(!y) c[p] = w[p] = ot[p] = 0;
			else c[p] = 1, w[p] = x % MOD, ot[p] = y - 1;
			return;
		}
		if(x <= mid) upd(ls[p], l, mid, x, y);
		else upd(rs[p], mid + 1, r, x, y);
		pushup(p, l, r);
	}
	#undef mid
} T;
posted @ 2026-05-13 16:30  So_noSlack  阅读(18)  评论(0)    收藏  举报