題解 CF997E 【Good Subsegments】
阿新 • • 發佈: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; }