【CF997E】Good Subsegments (线段树+单调栈)

Description

原题链接
给你一个长度为\(n\)的排列\(~P\),定义一段子区间是好的,当且仅当这个子区间内的值构成了连续的一段。例如对于排列\(\{1,3,2 \}\)\([1, 1], [2, 2], [3, 3], [2, 3], [1, 3]\)是好的区间。
\(q\)次询问,每次询问\(L,R\), 求有多少\(L \leq l \leq r \leq R\),满足\([l, r]\)是好的区间。\(1 \leq n, q \leq 1.2 \times 10 ^ 5\).

Solution

可以发现,区间\([l, r]\)是好的,当且仅当\(~(Max_{i = l}^{r} - Min_{i = l} ^ {r}) - (r - l) = 0\).
考虑维护一段区间上式的最小值(以下的最小值都指上式最小值),每一个最小值为\(0\)的位置都可以和当前的\(r\)组成一个好的区间。不难发现,一个右端点能产生的贡献为它左边的点的最小值为\(0\)的个数,于是可以在线段树上维护最小值和最小值个数,以及每个最小值为\(0\)的位置产生的贡献。
由于右边新加的点会对已有的点产生影响,考虑离线询问,按右端点排序,用两个单调栈分别维护当前\(Min\)\(Max\)。右端点右移时,势必会使整个区间的最小值减一,也势必会使其未右移前的右端点对答案产生一轮贡献,每次处理右端点等于当前枚举点的答案。时间复杂度\(O(n \log n)\).

Code

#include <bits/stdc++.h>

#define For(i, j, k) for (int i = j; i <= k; ++ i)
#define Forr(i, j, k) for (int i = j; i >= k; -- i)

using namespace std;

typedef long long ll;

inline int read() {
	int x = 0, p = 1; char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') p = -1;
	for (; isdigit(c); c = getchar()) x = (x << 1) + (x << 3) + (c ^ 48);
	return x * p;
}

inline void File() {
	freopen("CF997E.in", "r", stdin);
	freopen("CF997E.out", "w", stdout);
}

const int N = 1.2e5 + 10;
int n, q, a[N], s1[N], t1, s2[N], t2, tt = 1; ll ans[N];

struct Query {
	int id, l, r;
	bool operator < (const Query &rhs) const { return r < rhs.r; }
} Q[N];

namespace Segment_Tree {
#define lc (rt << 1)
#define rc (rt << 1 | 1)
#define mid (l + r >> 1)

	const int MAXN = N << 2;
	int mn[MAXN], tag[MAXN], t[MAXN]; ll sum[MAXN], tg[MAXN];
	
	inline void pushdown(int rt) {
		if (tag[rt]) {
			mn[lc] += tag[rt], mn[rc] += tag[rt];
			tag[lc] += tag[rt], tag[rc] += tag[rt];
			tag[rt] = 0;
		}

		if (tg[rt]) {
			if (mn[lc] == mn[rt]) sum[lc] += 1ll * t[lc] * tg[rt], tg[lc] += tg[rt];
			if (mn[rc] == mn[rt]) sum[rc] += 1ll * t[rc] * tg[rt], tg[rc] += tg[rt];
			tg[rt] = 0;
		}
	}

	inline void pushup(int rt) {
		t[rt] = 0, mn[rt] = min(mn[lc], mn[rc]);
		
		t[rt] = mn[rt] == mn[lc] ? t[rt] + t[lc] : t[rt];
		t[rt] = mn[rt] == mn[rc] ? t[rt] + t[rc] : t[rt];

		sum[rt] = sum[lc] + sum[rc];
	}

	inline void Build(int rt, int l, int r) {
		mn[rt] = l, t[rt] = 1;
		if (l ^ r) Build(lc, l, mid), Build(rc, mid + 1, r);
	}

	inline void update(int rt, int l, int r, int L, int R, int v) {
		if (L <= l && r <= R) { mn[rt] += v, tag[rt] += v; return ; }
		pushdown(rt); if (L <= mid) update(lc, l, mid, L, R, v);
		if (R > mid) update(rc, mid + 1, r, L, R, v); pushup(rt);
	}	

	inline ll query(int rt, int l, int r, int L, int R) {
		if (L <= l && r <= R) return sum[rt]; pushdown(rt);
		if (R <= mid) return query(lc, l, mid, L, R);
		if (L > mid) return query(rc, mid + 1, r, L, R);
		return query(lc, l, mid, L, R) + query(rc, mid + 1, r, L, R);
	}

#undef lc
#undef rc
#undef mid
}

int main() {
	File();

	using namespace Segment_Tree;

	n = read(); For(i, 1, n) a[i] = read();
	q = read(); For(i, 1, q) Q[i].l = read(), Q[i].r = read(), Q[i].id = i;

	sort(Q + 1, Q + 1 + q);

	Build(1, 1, n);

	For(nr, 1, n) {

		mn[1] -= 1, tag[1] -= 1;

		for (; t1 && a[s1[t1]] < a[nr]; -- t1)
			update(1, 1, n, s1[t1 - 1] + 1, s1[t1], a[nr] - a[s1[t1]]);
		s1[++ t1] = nr;
		
		for (; t2 && a[s2[t2]] > a[nr]; -- t2)
			update(1, 1, n, s2[t2 - 1] + 1, s2[t2], a[s2[t2]] - a[nr]);
		s2[++ t2] = nr;

		sum[1] += t[1], tg[1] += 1;

		for (; tt <= q && Q[tt].r == nr; ++ tt)
			ans[Q[tt].id] = query(1, 1, n, Q[tt].l, nr);
	}

	For(i, 1, q) printf("%lld\n", ans[i]);

	return 0;
}

posted @ 2018-10-08 22:12  LSTete  阅读(587)  评论(0编辑  收藏  举报