[HNOI2016] 序列
题目链接:
题目分析:
看到询问跳来跳去,各个询问之间的计算又有重叠部分,考虑能不能莫队一下
麻烦的是如何\(O(1)\)求\(l,r\)指针挪动一格的时候更新答案
以考虑挪动右指针为例
\([l, r] \rightarrow [l, r + 1]\)
新产生的区间是所有以\(r + 1\)为右端点,左端点在\([l, r + 1]\)内的所有区间
然后我们发现最小值是一段一段出现的
举个例子
2 1 6 3 4 5
假设我们固定右端点,把左端点从1挪到5,那么对应每个区间的最小值就是
1 1 3 3 4 5
可以发现不仅成段而且还递增,这个是易证的
考虑能不能预处理出每一段的起始结束位置,计算贡献就可以用\((最小值覆盖区间长度)*当前最小值\)来计算了
有一个常用的\(trick\)是处理出每个数前面第一个比它小的数\(pre_i\),这个可以用单调栈做到\(O(n)\)
然后可以得到一个式子,其中\(f_i\)表示右端点钦定为\(i\),左端点位于\(1\)到\(i\)的所有区间的最小值之和
\(f_i = f_{pre_i} + (i - pre_i) * a[i]\)
即当左端点在\(pre_i\)前面的时候,我们直接把\(pre_i\)的答案继承过来,然后加上这一段的新答案,即\(pre_i\)到\(i\)这一段都是以\(a_i\)为最小值的贡献
这个东西感性理解上是可减的,洛谷题解有一篇我觉得比较有道理,从容斥的角度去理解
把这个东西预处理出来,然后每次右指针右挪时候先查找\([l, r+1]\)内的最小值位置\(pos\),这样左端点在\([l, pos]\)内的单个区间贡献都是\(a_{pos}\),剩下的可以用\(f\)得到
\(ans = (pos - l + 1) * a[pos] + f[r + 1] - f[pos]\)
减的话就是乘一个\(-1\)的系数,左边的对称处理一下就好
代码:
代码跑得很慢,我也不知道为什么,可能这个莫队假了吧……得吸氧才能过
#include<bits/stdc++.h>
//#define int long long
using namespace std;
inline int read() {
int cnt = 0, f = 1; char c = getchar();
while (!isdigit(c)) {if (c == '-') f = -f; c = getchar();}
while (isdigit(c)) {cnt = (cnt << 3) + (cnt << 1) + (c ^ 48); c = getchar();}
return cnt * f;
}
const int N = (int)2e5 + 5;
typedef long long ll;
int n, m, pos[N];
ll ans[N], res, a[N];
struct Query {
int l, r, id;
} Q[N];
bool cmp(Query a, Query b) {
return pos[a.l] == pos[b.l] ? a.r < b.r : pos[a.l] < pos[b.l];
}
int ST[22][N], g[22][N];
void ST_prework() {
for (register int i = 1; i <= n; ++i) ST[0][i] = a[i], g[0][i] = i;
int t = log(n) / log(2);
for (register int j = 1; j <= t; ++j)
for (register int i = 1; i <= n - (1 << j) + 1; ++i) {
ST[j][i] = ST[j - 1][i] < ST[j - 1][i + (1 << (j - 1))] ? ST[j - 1][i] : ST[j - 1][i + (1 << (j - 1))];
g[j][i] = ST[j - 1][i] < ST[j - 1][i + (1 << (j - 1))] ? g[j - 1][i] : g[j - 1][i + (1 << (j - 1))];
}
}
int query(int L, int R) {
int k = log(R - L + 1) / log(2);
return ST[k][L] < ST[k][R - (1 << k) + 1] ? g[k][L] : g[k][R - (1 << k) + 1];
}
int sta[N], top, pre[N], suf[N], l, r;
ll f1[N], f2[N];
void prework() {
for (register int i = 1; i <= n; ++i) pre[i] = 0, suf[i] = n + 1;
for (register int i = 1; i <= n; ++i) {
while (a[sta[top]] > a[i] && top) --top;
pre[i] = sta[top];
sta[++top] = i;
}
for (register int i = 1; i <= n; ++i)
f1[i] = f1[pre[i]] + a[i] * (i - pre[i]);
top = 0;
for (register int i = n; i >= 1; --i) {
while (a[sta[top]] > a[i] && top) --top;
suf[i] = sta[top];
sta[++top] = i;
}
for (register int i = n; i >= 1; --i)
f2[i] = f2[suf[i]] + a[i] * (suf[i] - i);
}
void modify1(int L, int R, int k) {
int p = query(L, R);
res += k * ((R - p + 1) * a[p] + f2[L] - f2[p]);
}
void modify2(int L, int R, int k) {
int p = query(L, R);
res += k * ((p - L + 1) * a[p] + f1[R] - f1[p]);
}
signed main() {
// freopen("1.in", "r", stdin);
n = read(), m = read();
for (register int i = 1; i <= n; ++i) a[i] = read();
a[0] = a[n + 1] = (int)1e9 + 7;
ST_prework(); prework();
for (register int i = 1; i <= m; ++i)
Q[i].l = read(), Q[i].r = read(), Q[i].id = i;
int base = sqrt(m);
for (register int i = 1; i <= m; ++i) pos[Q[i].l] = Q[i].l / base;
sort (Q + 1, Q + m + 1, cmp);
l = 1, r = 1, res = 0;
res += a[1];
for (register int i = 1; i <= m; ++i) {
while (l > Q[i].l) modify1(--l, r, 1);
while (r < Q[i].r) modify2(l, ++r, 1);
while (l < Q[i].l) modify1(l++, r, -1);
while (r > Q[i].r) modify2(l, r--, -1);
ans[Q[i].id] = res;
}
for (register int i = 1; i <= m; ++i) printf("%lld\n", ans[i]);
return 0;
}