[HNOI 2016] 序列

题目

传送门

解法

妙妙题。


可以先考虑只有一个询问,询问区间为 ([1,n]) 该怎么做。不过这个和正解没什么关系,可以跳过。

显然对于每种权值 (k),用单调队列求出距离其下标最近的两个比它小的权值,这中间夹的就是贡献为 (k) 的区间。

时间复杂度 (mathcal O(n))


不过这道题不行。因为当 (l,r) 改变时,区间内每种权值都需要维护,时间就炸了。

试试莫队?

既然是莫队,我们就应该先考虑如何向外拓展。先假设原区间为 ([l,r]),我们拓展至 (r+1)

那么答案就是 (∑min{[i,r+1]} (lle ile r+1))

(f[i]) 为以 (i) 为结尾的所有区间的 (min) 之和。

(f[i]) 挺好算,我们找到 (i) 前面第一个比 (a[i]) 小的数的下标记为 (pos)。那么递推式就是:

[f[i]=f[pos]+a[i] imes (i-pos) ]

关于 (pos),用单调递增队列维护即可。

我们找出 ([l,r]) 中最小值的下标 (p),类似上面的 (mathtt{DP}) 就有

[Delta=a[p] imes (p-l+1)+f[r]-f[p] ]

由于 (a[p])([l,r]) 中最小值,所以 ((p,r]) 区间内的值对包含位置 (p) 的区间没有任何影响,也即没有改变。所以剩下的就正好是左端点在 ((p,r]) 的区间的答案。

类似地,我们设 (g[i]) 为以 (i) 为开始的所有区间的 (min) 之和。这里就不再赘述。

时间复杂度 (mathcal O(nsqrt n))

接下来我们谈谈莫队玄学的坑。

  • 首先四个循环时我们应该先加 (R),再加 (L);先减 (L),再减 (R)。不然可能会出现 (L>R) 的悲惨情况。尤其是莫队的奇偶优化,有时循环打错了会造成朴素莫队过得了,优化过不了的情况。(应该是优化使得右端点回退次数多了,造成 (L>R)
  • 有些题虽然可以用 long long 输出,但是中途莫队可能会爆,所以满足上述操作时,保险将加值与减值轮换操作。

总结

观察上面两种方法,其实就是因为限制有 权值区间,枚举其中一项,尝试快速获得另一项。

代码终于来辽!

代码

#include <cmath>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;

const int N = 1000100, lim = 2e9;

int rmq[N][22], lg[N], q[N], n, m, a[N], pos[N], block, minn[N][2];
ll ans, cnt[N], res[N], f[N][2];
struct node {
    int l, r, id;
}s[N];

int read() {
    int x = 0, f = 1; char s;
    while((s = getchar()) > '9' || s < '0') if(s == '-') f = -1;
    while(s >= '0' && s <= '9') {x = (x << 1) + (x << 3) + (s ^ 48); s = getchar();}
    return x * f;
}

int Cmp(const int x, const int y) {return a[x] <= a[y] ? x : y;}

bool cmp(const node a, const node b) {return (pos[a.l] ^ pos[b.l]) ? a.l < b.l : ((pos[a.l] & 1) ? a.r < b.r : a.r > b.r);}

int get(const int l, const int r) {
    int len = r - l + 1;
    return Cmp(rmq[l][lg[len]], rmq[r - (1 << lg[len]) + 1][lg[len]]);
}

void rig(const int l, const int r, const ll op) {
    int p = get(l, r);
    ans += 0ll + op * (p - l + 1) * a[p] + op * (f[r][0] - f[p][0]);
}

void lef(const int l, const int r, const ll op) {
    int p = get(l, r);
    ans += 0ll + op * (r - p + 1) * a[p] + op * (f[l][1] - f[p][1]);
}

void MD() {
    int L = 1, R = 0;
    for(int i = 1; i <= m; ++ i) {
        while(R < s[i].r) rig(L, R + 1, 1ll), ++ R;
        while(L < s[i].l) lef(L, R, -1ll), ++ L;
        while(L > s[i].l) lef(L - 1, R, 1ll), -- L;
        while(R > s[i].r) rig(L, R, -1ll), -- R;
        res[s[i].id] = ans;
    }
}

void init() {
    block = sqrt(n);
    for(int i = 1; i <= n; ++ i) pos[i] = (i - 1) / block + 1;
    int l = 1, r = 1; a[0] = a[n + 1] = -lim;
    for(int i = 1; i <= n; ++ i) {
        while(l <= r && a[q[r]] >= a[i]) -- r;
        minn[i][0] = q[r];
        q[++ r] = i;
    }
    l = 1, r = 1; q[1] = n + 1;
    for(int i = n; i >= 1; -- i) {
        while(l <= r && a[q[r]] >= a[i]) -- r;
        minn[i][1] = q[r];
        q[++ r] = i;
    }
    for(int i = 1; i <= n; ++ i) f[i][0] = f[minn[i][0]][0] + 1ll * a[i] * (i - minn[i][0]);
    for(int i = n; i >= 1; -- i) f[i][1] = f[minn[i][1]][1] + 1ll * a[i] * (minn[i][1] - i);
    for(int i = 2; i <= n; ++ i) lg[i] = lg[i >> 1] + 1;
    for(int i = 1; i <= n; ++ i) rmq[i][0] = i;
    for(int j = 1; j <= 20; ++ j)
        for(int i = 1; i <= n; ++ i)
            rmq[i][j] = Cmp(rmq[i][j - 1], rmq[i + (1 << j - 1)][j - 1]);
}

int main() {
    n = read(), m = read();
    for(int i = 1; i <= n; ++ i) a[i] = read();
    for(int i = 1; i <= m; ++ i) s[i].l = read(), s[i].r = read(), s[i].id = i;
    init();
    sort(s + 1, s + m + 1, cmp);
    MD();
    for(int i = 1; i <= m; ++ i) printf("%lld
", res[i]);
    return 0;
}

原文地址:https://www.cnblogs.com/AWhiteWall/p/12467870.html