可以先做一下弱化版: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;
}