[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;
}
posted @ 2019-12-14 01:30  kma_093  阅读(132)  评论(0编辑  收藏  举报