[HNOI2016]序列

只有询问,允许离线,我们可以考虑使用莫队。

那么问题在于我们怎么计算右端点 \(+1\) 带来的增量,可以发现这些新增加的子区间都是以 \(r\) 为右端点的,那么我们只需要考虑以 \(r\) 为右端点,左端点在 \([l, r]\) 的区间最小值之和,可以发现这个的答案是可以转移的,于是我们令 \(dp_{l, r}\) 为上面这个最小值之和,那么就有转移:

\[dp_{l, r} = dp_{l, {li}_i} + (i - {li}_i) \times a_i \]

\({li}_i\)\(i\) 左边第一个比 \(i\) 小的位置,这个可以使用单调栈求出。可以发现上面那个 \(dp\) 的边界应该是 \(dp_{l, p} = a_p \times (p - l + 1)\),其中 \(p\)\([l, r]\) 中最小值所在的位置。但是上面这个 \(dp\) 对于每个 \(l\) 是不一样的,我们显然不能对每个 \(l\) 预处理出每个 \(r\) 的答案,但是仔细分析可以发现不论 \(l\) 是什么每次我们转移到的地方和转移产生的增量都只与 \(i\) 有关是个定值,并且我们最后肯定是转移到某个位置 \(x\),然后再转移到 \(p\) 结束转移,可以发现我们要求的 \(dp_{l, r} = a_p \times (p - l + 1) + a_x \times(x - p) + \cdots + a_r \times (r - {li}_r)\),而对于每个区间 \(a_p \times (p - l + 1)\) 是已知的,我们所需要知道的实际上是后一部分 \(a_x \times(x - p) + \cdots + a_r \times (r - {li}_r)\) 增量的和,因为不论 \(l\) 是什么中间的增量都是一定的,那么我们何不将 \(l\) 设置为 \(1\),那么中间的这部分增量就可以表示成 \(dp_{1, r} - dp_{1, p}\) 了。总地来说,我们预处理出 \(f_i\) 表示以 \(i\) 为右端点,左端点在 \([1, i]\) 的答案,那么以 \(r\) 为右端点,左端点在 \([l, r]\) 中的答案就可以表示为 \(f_r - f_p + a_p \times (p - l + 1)\),这样我们就解决了右端点 \(+1\) 时对答案的贡献,考虑左端点移动时对答案的贡献只需将序列翻转过来看待即可。

一些坑点

  • 我们先要扩大区间再减小区间,因为如果中途出现了 \(l > r\) 会导致答案计算错误。

  • \(st\) 表倍增预处理时最好精确到最大的 \(log\),并且将第一维空间开两倍或者直接在 \(i + (1 << (j - 1))\) 时特判。

#include<bits/stdc++.h>
using namespace std;
#define N 100000 + 5
#define M 20 + 5
#define rep(i, l, r) for(int i = l; i <= r; ++i)
#define dep(i, l, r) for(int i = r; i >= l; --i)
struct node{
    int l, r, p;
}q[N];
long long tmp, ans[N], pre[N], suf[N];
int n, l, r, Q, top, size, a[N], li[N], ri[N], st[N], block[N], f[3 * N][M];
int read(){
    char c; int x = 0, f = 1;
    c = getchar();
    while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
bool cmp(node a, node b){
    return block[a.l] == block[b.l] ? ((block[a.l] & 1) ? a.r < b.r : a.r > b.r) : a.l < b.l;
}
int query(int l, int r){
    int D = log2(r - l + 1);
    return a[f[l][D]] < a[f[r - (1 << D) + 1][D]] ? f[l][D] : f[r - (1 << D) + 1][D];
}
void updatel(int p, int w){
    int mxp = query(p, r);
    tmp += 1ll * w * (suf[p] - suf[mxp] + 1ll * a[mxp] * (r - mxp + 1)); 
}
void updater(int p, int w){
    int mxp = query(l, p);
    tmp += 1ll * w * (pre[p] - pre[mxp] + 1ll * a[mxp] * (mxp - l + 1)); 
}
signed main(){
    n = read(), Q = read(), size = sqrt(n); 
    rep(i, 1, n) a[i] = read(), f[i][0] = i, block[i] = ceil(1.0 * i / size);
    rep(j, 1, 17) rep(i, 1, n){
        if(a[f[i][j - 1]] < a[f[i + (1 << (j - 1))][j - 1]]) f[i][j] = f[i][j - 1];
        else f[i][j] = f[i + (1 << (j - 1))][j - 1];
    }
    dep(i, 1, n){
        while(top && a[st[top]] > a[i]) li[st[top]] = i, --top;
        st[++top] = i;
    }
    top = 0;
    rep(i, 1, n){
        while(top && a[st[top]] > a[i]) ri[st[top]] = i, --top;
        st[++top] = i;
    }
    rep(i, 1, n) pre[i] = pre[li[i]] + 1ll * (i - li[i]) * a[i];
    dep(i, 1, n) suf[i] = suf[ri[i]] + 1ll * (ri[i] - i) * a[i];
    rep(i, 1, Q) q[i].l = read(), q[i].r = read(), q[i].p = i;
    sort(q + 1, q + Q + 1, cmp);
    l = 1, r = 0;
    rep(i, 1, Q){
        while(r < q[i].r) updater(++r, 1);
        while(l > q[i].l) updatel(--l, 1);
        while(r > q[i].r) updater(r--, -1);
        while(l < q[i].l) updatel(l++, -1);
        ans[q[i].p] = tmp;
    }
    rep(i, 1, Q) printf("%lld\n", ans[i]);
    return 0;
}

可以发现上面那个 \(dp\) 本身是不依赖于莫队的,有没有一种更为简单的方法呢?实际上是有的,可以发现对于一次询问我们实际上是在求这个式子:

\[\sum\limits_{i = l} ^ r dp_{l, i} \]

因为我们已经知道 \(dp_{p + 1, i}\) 怎么算,不妨将上式的贡献来源拆分成三个部分:区间跨过 \(p\) 的贡献,即 \(a_p \times (p - l + 1) \times (r - p + 1)\);两个端点均在 \(p\) 右边的情况,即 \(\sum\limits_{i = p + 1} ^ r dp_{p + 1, i} = \sum\limits_{i = p + 1} ^ r f_i - f_p\) 于是只需记录 \(pre_i\)\(f_j\) 的前缀和即可;两个端点均在 \(p\) 左边的情况,同理于上一种情况,将序列翻转来看即可。于是我们可以得到如下代码:

#include<bits/stdc++.h>
using namespace std;
#define N 100000 + 5
#define M 20 + 5
#define rep(i, l, r) for(int i = l; i <= r; ++i)
#define dep(i, l, r) for(int i = r; i >= l; --i)
unsigned long long S, A, B, C, ans, lastans = 0, f[N], g[N], pre[N], suf[N];
int n, l, r, Q, top, type, a[N], li[N], ri[N], st[N], dp[3 * N][M];
int read(){
    char c; int x = 0, f = 1;
    c = getchar();
    while(c > '9' || c < '0'){ if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
unsigned long long Random(){
	return S ^= (A + B * lastans) % C;
}
int query(int l, int r){
    int D = log2(r - l + 1);
    return a[dp[l][D]] < a[dp[r - (1 << D) + 1][D]] ? dp[l][D] : dp[r - (1 << D) + 1][D];
}
signed main(){
    n = read(), Q = read(), type = read();
    rep(i, 1, n) a[i] = read(), dp[i][0] = i;
    rep(j, 1, 17) rep(i, 1, n){
        if(a[dp[i][j - 1]] < a[dp[i + (1 << (j - 1))][j - 1]]) dp[i][j] = dp[i][j - 1];
        else dp[i][j] = dp[i + (1 << (j - 1))][j - 1];
    }
    dep(i, 1, n){
        while(top && a[st[top]] > a[i]) li[st[top]] = i, --top;
        st[++top] = i;
    }
    top = 0;
    rep(i, 1, n){
        while(top && a[st[top]] > a[i]) ri[st[top]] = i, --top;
        st[++top] = i;
    }
    rep(i, 1, n) f[i] = f[li[i]] + 1ull * (i - li[i]) * a[i], pre[i] = pre[i - 1] + f[i];
    dep(i, 1, n) g[i] = g[ri[i]] + 1ull * (ri[i] - i) * a[i], suf[i] = suf[i + 1] + g[i];
    if(type) S = read(), A = read(), B = read(), C = read();
    while(Q--){
        if(!type) l = read(), r = read();
        else{ l = Random() % n + 1, r = Random() % n + 1; if(l > r) swap(l, r);}
        int p = query(l, r);
        lastans = pre[r] - pre[p - 1] - f[p] * (r - p + 1);
        lastans += suf[l] - suf[p] - g[p] * (p - l);
        lastans += 1ull * (r - p + 1) * (p - l + 1) * a[p];
        ans ^= lastans;
    }
    printf("%llu", ans);
    return 0;
}
posted @ 2020-08-11 21:34  Achtoria  阅读(106)  评论(0编辑  收藏  举报