題解 CF997E 【Good Subsegments】

JiaZP發表於2020-07-26

可以先做一下弱化版:CF526F Pudding Monsters,那道題是本題的基礎。

由於這是個排列,因此好區間可以轉化為滿足 \(max - min = r - l\) 的區間。其中 \(max,min\) 分別表示區間最大值和最小值,\(l,r\) 分別表示區間左右端點。我們可以列舉 \(r\),那麼限制變為 \(max - min + l = r\)。又因為對於所有區間,都有 \(max - min >= r - l\),所以好區間可以轉化為“以 \(r\) 為右端點,且 \(max - min + l\) 最小左端點的個數”。

我們從左往右掃一遍,用單調棧和線段樹維護最值,掃的時候順便統計一下全域性最小值個數,這就是前面那道題的做法。

對於這道題,我們可以離線,將詢問掛到右端點,“子區間”轉化為“字首的字尾”。如果只考慮一個字首的所有字尾的答案,這題只不過多限制左端點的範圍,不能小於 \(L\),這個好說,查全域性最小值個數改為查區間最小值個數即可。再考慮所有字首的貢獻,這個可以直接線上段樹上打“歷史貢獻”的標記,查詢就把節點的“歷史貢獻”加和即可。

總結一下,我們需要一棵線段樹,支援區間加,單點修改,區間查歷史貢獻。還需要倆單調棧,維護最大最小值,並線上段樹上進行操作。

細節看程式碼吧。

\(Code:\)

#define N 201000
#define NN 801000
#define int long long
template <typename T> inline void read(T &x) {
	x = 0; char c = getchar(); bool flag = false;
	while (!isdigit(c)) { if (c == '-')	flag = true; c = getchar(); }
	while (isdigit(c)) { x = (x << 1) + (x << 3) + (c ^ 48); c = getchar(); }
	if (flag)	x = -x;
}
using namespace std;
const int inf = 987654321;
int n;
int h[N];
struct edge{
	int nxt, to, id;
}e[N];
int head[N], ecnt;
inline void addedge(int from, int to, int id) {//鄰接表掛詢問
	e[++ecnt] = (edge){head[from], to, id};
	head[from] = ecnt;
}
int ans[N];

struct node {
	int mn, cnt;
	node(int mnn = inf, int cntt = 0) { mn = mnn, cnt = cntt; }
	node operator +(const node a) const {
		return node(min(mn, a.mn), mn == a.mn ? cnt + a.cnt : (mn < a.mn ? cnt : a.cnt));
	}
}nd[NN];
int ls[NN], rs[NN], atag[NN], ctag[NN], res[NN], root, ttot;
void build(int L, int R, int &cur) {
	cur = ++ttot;
	if (L == R)	return ;
	int mid = (L + R) >> 1;
	build(L, mid, ls[cur]);
	build(mid + 1, R, rs[cur]);
}
inline void pushup(int cur) {
	nd[cur] = nd[ls[cur]] + nd[rs[cur]];
}
inline void pusha(int cur, int v) {//打加法標記
	if (!cur)	return ;
	nd[cur].mn += v;
	atag[cur] += v;
}
inline void pushc(int cur, int mn, int c) {//打歷史標記
	if (!cur || nd[cur].mn != mn)	return ;
    //只有兒子最小值和父親相同時才能繼承貢獻
	res[cur] += nd[cur].cnt * c;
	ctag[cur] += c;
}
inline void pushdown(int cur) {//下放標記。注意順序
	if (atag[cur])
		pusha(ls[cur], atag[cur]), pusha(rs[cur], atag[cur]), atag[cur] = 0;
	if (ctag[cur])
		pushc(ls[cur], nd[cur].mn, ctag[cur]), 
		pushc(rs[cur], nd[cur].mn, ctag[cur]), 
		ctag[cur] = 0;
}
void modify(int L, int R, int l, int r, int v, int cur) {//區間加
	if (l <= L && R <= r) {
		pusha(cur, v);
		return ;
	}
	pushdown(cur);
	int mid = (L + R) >> 1;
	if (l <= mid)	modify(L, mid, l, r, v, ls[cur]);
	if (r > mid)	modify(mid + 1, R, l, r, v, rs[cur]);
	pushup(cur);
}
void modify(int L, int R, int pos, int v, int cur) {//單點修改
	if (L == R)	return nd[cur] = (node){v, 1}, void();
	pushdown(cur);
	int mid = (L + R) >> 1;
	if (pos <= mid)	modify(L, mid, pos, v, ls[cur]);
	else	modify(mid + 1, R, pos, v, rs[cur]);
	pushup(cur);
}
int query(int L, int R, int l, int r, int cur) {
	if (l <= L && R <= r)	return res[cur];
	pushdown(cur);
	int mid = (L + R) >> 1, tmp = 0;
	if (l <= mid)	tmp = query(L, mid, l, r, ls[cur]);
	if (r > mid)	tmp += query(mid + 1, R, l, r, rs[cur]);
	return tmp;
}

struct Seg {//單調棧
	int l, r, v;//l ~ r 的最值都是 v
	Seg(int ll = 0, int rr = 0, int vv = 0) { l = ll, r = rr, v = vv; }
	bool operator <(const Seg a) const {
		return v < a.v;
	}
	bool operator >(const Seg a) const {
		return v > a.v;
	}
}smx[N], smn[N];
int mxtop, mntop;

signed main() {
	read(n);
	for (register int i = 1; i <= n; ++i)	read(h[i]);
	int q; read(q);
	for (register int i = 1; i <= q; ++i) {
		int l, r; read(l), read(r);
		addedge(r, l, i);
	}
	build(1, n, root);
	for (register int i = 1; i <= n; ++i) {
    	//維護最值
		Seg s(i, i, h[i]);
		while (mxtop && s > smx[mxtop])
			modify(1, n, smx[mxtop].l, smx[mxtop].r, h[i] - smx[mxtop].v, root),
			s.l = smx[mxtop].l, --mxtop;
		smx[++mxtop] = s;
		
		s = Seg(i, i, h[i]);
		while (mntop && s < smn[mntop])
			modify(1, n, smn[mntop].l, smn[mntop].r, -h[i] + smn[mntop].v, root),
			s.l = smn[mntop].l, --mntop;
		smn[++mntop] = s;
		
		modify(1, n, i, i, root);//插入新點
		
		pushc(root, i, 1);//將當前的合法區間計入貢獻
		for (register int j = head[i]; j; j = e[j].nxt) {
			int l = e[j].to, id = e[j].id;
			ans[id] = query(1, n, l, i, root);
		}
	}
	for (register int i = 1; i <= q; ++i)
		printf("%lld\n", ans[i]);
	return 0;
}

相關文章