[HNOI2016] 序列
题解:\(ST\)表 + 莫队
- 设莫队维护区间\([l,r]\)的答案\(ans\),我们考虑右端点\(r\)向右扩张时\(r:=r+1\)对\(ans\)的影响,设\(min[l,r]\)代表区间\([l,r]\)中的最小值
- \(ans :=ans+min[r,r]+min[r-1,r]+min[r-2,r]+...+min[l,r]\)
- 设\([l,r]\)中最小值的位置为\(p\),则\(min[i,r] = a[p],i\in[l,p]\),所以\(ans:=ans+a[p]\times(p-l+1)+min[r,r]+...+min[p+1,r]\)
- 我们令\(pre[i]\)为左边比\(a[i]\)第一个小的位置,\(nxt[i]\)为右边比\(a[i]\)第一个小的位置,\(fr[i]:\sum_{l=1}^{i}min[l,i]\),\(fl[i]:\sum_{r=n}^{i}min[i,r]\)
- \(fr[i] = fr[pre[i]]+(i-pre[i])\times a[i]\),\(fl[i] = fl[nxt[i]]+(nxt[i]-i)\times a[i]\)
- 我们发现左端点在\([pre[r],r]\)的最小值都是\(a[r]\),那么对答案的贡献为\((r - pre[r])\times a[r]\)
- 以此类推,一定存在一个点\(x\),使得\(pre[x] = p\)
- 所以\(ans:=ans+a[p]\times(p-l+1)+(x-pre[x])\times a[x]+...+(r-pre[r])\times a[r]\)
- 容易发现\((x-pre[x])\times a[x]+...+(r-pre[r])\times a[r]=fr[r]-fr[p]\)
- 所以\(ans:=ans+a[p]\times(p-l+1) + fr[r]-fr[p]\)
- 那么对于一个端点的扩张我们可以\(O(1)\)更新答案
- 单调栈预处理\(pre[i],nxt[i]\),\(ST\)表\(O(nlogn)\)预处理,\(O(1)\)查询最小值位置,\(O(n)\)预处理\(fl[i],fr[i]\)
- 复杂度:\(O(nlogn + n\sqrt{n})\)
const int N = 1e5 + 10, M = 4e5 + 10;
const int B = sqrt(N) + 1;
int n, q, a[N], nxt[N], pre[N], stk[N], top, fl[N], fr[N];
int id[N], ans[N], Ans, st[N][22], lg2[N];
struct QUERY
{
int l, r, idx;
bool operator<(const QUERY &t) const
{
if (id[l] == id[t.l])
{
if (id[l] & 1)
return r < t.r;
else
return r > t.r;
}
else
return l < t.l;
}
} qry[N];
void build()
{
for (int i = 1; i <= n; ++i)
st[i][0] = i;
for (int i = 2; i <= n; ++i)
lg2[i] = lg2[i >> 1] + 1;
for (int j = 1; j <= 20; ++j)
for (int i = 1; i + (1ll << j) - 1 <= n; ++i)
st[i][j] = (a[st[i][j - 1]] < a[st[i + (1ll << (j - 1))][j - 1]] ? st[i][j - 1] : st[i + (1ll << (j - 1))][j - 1]);
}
int query(int l, int r)
{
int len = lg2[r - l + 1];
return (a[st[l][len]] < a[st[r - (1ll << len) + 1][len]] ? st[l][len] : st[r - (1ll << len) + 1][len]);
}
void del(int l, int r, int op)
{
int p = query(l, r);
if (op == 1)
Ans -= a[p] * (r - p + 1) + fl[l] - fl[p];
else
Ans -= a[p] * (p - l + 1) + fr[r] - fr[p];
}
void add(int l, int r, int op)
{
int p = query(l, r);
if (op == 1)
Ans += a[p] * (r - p + 1) + fl[l] - fl[p];
else
Ans += a[p] * (p - l + 1) + fr[r] - fr[p];
}
void solve()
{
cin >> n >> q;
for (int i = 1; i <= n; ++i)
cin >> a[i];
// 初始化 ST表
build();
// 单调栈预处理 pre[i] : 左边第一个比a[i]小的数的位置
for (int i = 1, top = 0; i <= n; ++i)
{
while (top && a[stk[top]] >= a[i])
top--;
if (top)
pre[i] = stk[top];
else
pre[i] = 0;
stk[++top] = i;
}
// 单调栈预处理 nxt[i] : 右边第一个比a[i]小的数的位置
for (int i = n, top = 0; i >= 1; --i)
{
while (top && a[stk[top]] >= a[i])
top--;
if (top)
nxt[i] = stk[top];
else
nxt[i] = n + 1;
stk[++top] = i;
}
for (int i = 1; i <= n; ++i)
fr[i] = fr[pre[i]] + a[i] * (i - pre[i]);
for (int i = n; i >= 1; --i)
fl[i] = fl[nxt[i]] + a[i] * (nxt[i] - i);
for (int i = 1; i <= q; ++i)
{
cin >> qry[i].l >> qry[i].r;
qry[i].idx = i;
}
for (int i = 1; i <= n; ++i) // 分块
id[i] = (i - 1) / B + 1;
sort(qry + 1, qry + q + 1); // 对查询进行排序
for (int i = 1, l = 1, r = 0; i <= q; ++i)
{
while (l > qry[i].l)
add(--l, r, 1);
while (r < qry[i].r)
add(l, ++r, 2);
while (l < qry[i].l)
del(l++, r, 1);
while (r > qry[i].r)
del(l, r--, 2);
ans[qry[i].idx] = Ans;
}
for (int i = 1; i <= q; ++i)
cout << ans[i] << endl;
}