题解 CF997E 【Good Subsegments】
可以先做一下弱化版: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;
}