[BZOJ4540][Hnoi2016]序列(莫队/线段树)

解法一:莫队+RMQ

看到“子序列的子序列”这样的问题,首先想到莫队,即离线排序之后不断移动指针。下面以 [l,r1] [ l , r − 1 ] 转移到 [l,r] [ l , r ] 为例,分析转移的方法。
可以看出, [l,r] [ l , r ] 的结果比 [l,r1] [ l , r − 1 ] 的结果多了 ri=lminrj=ia[j] ∑ i = l r min j = i r a [ j ] ,也就是 [l,r] [ l , r ] [l+1,r] [ l + 1 , r ] [l+2,r] [ l + 2 , r ] ... . . . [r1,r] [ r − 1 , r ] [r,r] [ r , r ] rl+1 r − l + 1 个子序列的最小值之和。
可以看出,如果设 x=maxr1i=1i[a[i]<a[r]] x = max i = 1 r − 1 i [ a [ i ] < a [ r ] ] (也就是 x x 等于满足i<r a[i]<a[r] a [ i ] < a [ r ] 的最大的 i i ),那么左端点在[x+1,r]区间内,右端点为 r r 的所有rx个子序列的最小值都是 a[r] a [ r ]
同样也可以得到:如果设 y y 等于满足i<x a[i]<a[x] a [ i ] < a [ x ] 的最大的 i i ,那么左端点在[y+1,x]区间内,右端点为 r r 的所有xy个子序列的最小值都是 a[x] a [ x ]
这样,预处理出 pre[i] p r e [ i ] 等于满足 j<i j < i a[j]<a[i] a [ j ] < a [ i ] 的最大的 j j (可以用单调栈得出),那么可以预处理一个类似于前缀和的东西,即

sl[i]=sl[pre[i]]+(ipre[i])a[i]

这时候就很容易得出,如果设 k k 初始等于r,并且不断地使 k=pre[k] k = p r e [ k ] 可以让 k k 变成l1,那么:

i=lrminj=ira[j]=sl[r]sl[l1] ∑ i = l r min j = i r a [ j ] = s l [ r ] − s l [ l − 1 ]

而如果不能使 k k 变成l1,怎么办呢?考虑利用RMQ,找到 [l,r] [ l , r ] 位置最右的最小值的位置 w w ,这样左端点在[l,w]内,右端点为 r r 的所有wl+1个子序列的最小值都是 a[w] a [ w ]
此时就一定能使 k k 变成w。所以:
i=lrminj=ira[j]=(wl+1)a[w]+sl[r]sl[w] ∑ i = l r min j = i r a [ j ] = ( w − l + 1 ) ∗ a [ w ] + s l [ r ] − s l [ w ]

对于其他的转移也是类似了。但要注意一个细节:处理从 [l+1,r] [ l + 1 , r ] 转移到 [l,r] [ l , r ] 时,必须找出 [l,r] [ l , r ] 内位置 最左而不是最右的最小值位置,或者 suf[i] s u f [ i ] 记录满足 j>i j > i a[j]a[i] a [ j ] ≤ a [ i ] 的最小的 j j 而不是a[j]<a[i]
复杂度 O(nn) O ( n n )
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
typedef long long ll;
const int N = 1e5 + 5, LogN = 20;
int n, m, S, a[N], pre[N], suf[N], RMQ[N][LogN], Log[N], stk[N], top;
ll now, suml[N], sumr[N], res[N];
void init_rmq() {
    int i, j; Log[0] = -1;
    for (i = 1; i <= n; i++) Log[i] = Log[i >> 1] + 1, RMQ[i][0] = i;
    for (j = 1; j <= 18; j++)
        for (i = 1; i + (1 << j) - 1 <= n; i++) {
            int l = RMQ[i][j - 1], r = RMQ[i + (1 << j - 1)][j - 1];
            RMQ[i][j] = a[l] < a[r] ? l : r;
        }
    stk[top = 0] = 0; for (i = 1; i <= n; i++) {
        while (top && a[stk[top]] >= a[i]) top--;
        pre[stk[++top] = i] = stk[top - 1];
    }
    stk[top = 0] = n + 1; for (i = n; i; i--) {
        while (top && a[stk[top]] > a[i]) top--;
        suf[stk[++top] = i] = stk[top - 1];
    }
    for (i = 1; i <= n; i++) suml[i] = suml[pre[i]] + 1ll * a[i] * (i - pre[i]);
    for (i = n; i; i--) sumr[i] = sumr[suf[i]] + 1ll * a[i] * (suf[i] - i);
}
int qmin(int l, int r) {
    int x = Log[r - l + 1], y = RMQ[l][x], z = RMQ[r - (1 << x) + 1][x];
    return a[y] < a[z] ? y : z;
}
struct cyx {
    int l, r, bl, id; ll ans;
} que[N];
inline bool comp(const cyx &a, const cyx &b) {
    if (a.bl != b.bl) return a.bl < b.bl;
    return a.r < b.r;
}
ll queryle(int l, int r) {
    int x = qmin(l, r); if (x == l) return 1ll * a[x] * (r - l + 1);
    return 1ll * a[x] * (r - x + 1) + sumr[l] - sumr[x];
}
ll queryri(int l, int r) {
    int x = qmin(l, r); if (x == r) return 1ll * a[x] * (r - l + 1);
    return 1ll * a[x] * (x - l + 1) + suml[r] - suml[x];
}
int main() {
    int i; n = read(); m = read(); S = sqrt(n);
    for (i = 1; i <= n; i++) a[i] = read(); init_rmq();
    for (i = 1; i <= m; i++) que[i].l = read(), que[i].r = read(),
        que[i].id = i, que[i].bl = (que[i].l - 1) / S + 1;
    sort(que + 1, que + m + 1, comp); int l = 1, r = 0;
    for (i = 1; i <= m; i++) {
        int tl = que[i].l, tr = que[i].r;
        while (r < tr) now += queryri(l, ++r);
        while (l > tl) now += queryle(--l, r);
        while (r > tr) now -= queryri(l, r--);
        while (l < tl) now -= queryle(l++, r);
        que[i].ans = now;
    }
    for (i = 1; i <= m; i++) res[que[i].id] = que[i].ans;
    for (i = 1; i <= m; i++) printf("%lld\n", res[i]);
    return 0;
}

解法二:扫描线+线段树

同样,利用单调栈,预处理出:
prei p r e i :在 i i 的左边,值比ai小并且与 i i 最近的位置。
sufi:在 i i 的右边,值比ai小或与 ai a i 相等,并且与 i i 最接近的位置。
求整个序列的最小值之和,可以反过来考虑每个ai作为最小值出现了多少次。
整个序列的最小值之和为:

i=1n(iprei)×(sufii)×ai ∑ i = 1 n ( i − p r e i ) × ( s u f i − i ) × a i

把问题抽象到一个 n×n n × n 的二维平面上,对于每个坐标 (i,j) ( i , j )
如果 ij i ≤ j ,则坐标 (i,j) ( i , j ) 上的数为区间 [i,j] [ i , j ] 的最小值。
如果 i>j i > j ,则坐标 (i,j) ( i , j ) 上的数为 0 0
这样一个询问[l,r]相当于询问横坐标为 [l,n] [ l , n ] ,纵坐标为 [l,r] [ l , r ] 的矩形内的数之和。
预处里可以看成 n n 次修改:每次修改把横坐标为[prei+1,i],纵坐标为 [i,sufi1] [ i , s u f i − 1 ] 的矩形内的所有数加上 ai a i
这样(可能?)可以用二维线段树求解,但可以发现这样十分不可做。
考虑 扫描线+线段树。用一条垂直于 x x 轴的扫描线从右往左扫描,因为询问的横坐标范围都是[l,n]的形式,因此可以将询问按照横坐标范围的左端点( l l )从大到小排序,扫描线扫到x=l时,就可以回答对应的询问了。
但是对于修改还是不好操作。因此考虑用线段树维护,在每个位置维护一个一次函数:
y=kix+bi y = k i x + b i

使得扫描线扫到 x=l x = l 时, y y 坐标为i的数之和为 kil+bi k i l + b i
可以把一个修改拆成两个:
1、一个修改进入扫描线( x=i x = i )时,区间 [i,sufi1] [ i , s u f i − 1 ] 内的 k k 减去ai b b 加上(i+1)×ai
2、一个修改离开扫描线( x=prei x = p r e i )时,区间 [i,sufi1] [ i , s u f i − 1 ] 内的 k k 加上ai b b 减去(prei+1)×ai
将修改也按照进入或离开扫描线的位置从大到小排序,就能支持这个操作了。
复杂度 O(nlogn) O ( n log ⁡ n ) (自带大常数)
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define p2 p << 1
#define p3 p << 1 | 1
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
typedef long long ll;
const int N = 1e5 + 5, M = N << 1, L = M << 1;
int n, m, tot, a[N], pre[N], suf[N], top, stk[N]; ll ans[N];
struct cyx {
    int t, l, r; ll dx, dy; cyx() {}
    cyx(int _t, int _l, int _r, ll _dx, ll _dy) :
        t(_t), l(_l), r(_r), dx(_dx), dy(_dy) {}
} orz[M];
struct pyz {int l, r, id;} que[N]; ll sumx[L], sumy[L], addx[L], addy[L];
inline bool comp1(cyx &a, cyx &b) {return a.t > b.t;}
inline bool comp2(pyz &a, pyz &b) {return a.l > b.l;}
void down(int p) {
    addx[p2] += addx[p]; addx[p3] += addx[p]; addx[p] = 0;
    addy[p2] += addy[p]; addy[p3] += addy[p]; addy[p] = 0;
}
void upt(int l, int r, int p) {
    int mid = l + r >> 1; sumx[p] = sumx[p2] + sumx[p3] + addx[p2] *
    (mid - l + 1) + addx[p3] * (r - mid); sumy[p] = sumy[p2] + sumy[p3]
    + addy[p2] * (mid - l + 1) + addy[p3] * (r - mid);
}
void change(int l, int r, int s, int e, ll dx, ll dy, int p) {
    if (l == s && r == e) return (void) (addx[p] += dx, addy[p] += dy);
    int mid = l + r >> 1; down(p);
    if (e <= mid) change(l, mid, s, e, dx, dy, p2);
    else if (s >= mid + 1) change(mid + 1, r, s, e, dx, dy, p3);
    else change(l, mid, s, mid, dx, dy, p2),
        change(mid + 1, r, mid + 1, e, dx, dy, p3); upt(l, r, p);
}
ll ask(int l, int r, int s, int e, int x, int p) {
    if (l == s && r == e) return (sumx[p] + addx[p] * (r - l + 1))
        * x + sumy[p] + addy[p] * (r - l + 1);
    int mid = l + r >> 1; down(p); ll res;
    if (e <= mid) res = ask(l, mid, s, e, x, p2);
    else if (s >= mid + 1) res = ask(mid + 1, r, s, e, x, p3);
    else res = ask(l, mid, s, mid, x, p2) + ask(mid + 1, r, mid + 1, e, x, p3);
    return upt(l, r, p), res;
}
int main() {
    int i, j; n = read(); m = read(); for (i = 1; i <= n; i++) a[i] = read();
    stk[top = 0] = 0; for (i = 1; i <= n; i++) {
        while (top && a[stk[top]] >= a[i]) top--;
        pre[stk[++top] = i] = stk[top - 1];
    }
    stk[top = 0] = n + 1; for (i = n; i; i--) {
        while (top && a[stk[top]] > a[i]) top--;
        suf[stk[++top] = i] = stk[top - 1];
    }
    for (i = 1; i <= n; i++) {
        orz[++tot] = cyx(i, i, suf[i] - 1, -a[i], 1ll * (i + 1) * a[i]);
        orz[++tot] = cyx(pre[i], i, suf[i] - 1, a[i], -1ll * (pre[i] + 1) * a[i]);
    }
    for (i = 1; i <= m; i++) que[i].l = read(), que[i].r = read(), que[i].id = i;
    sort(orz + 1, orz + tot + 1, comp1); sort(que + 1, que + m + 1, comp2);
    for (i = j = 1; i <= m; i++) {
        while (j <= tot && orz[j].t >= que[i].l) change(1, n, orz[j].l,
            orz[j].r, orz[j].dx, orz[j].dy, 1), j++;
        ans[que[i].id] = ask(1, n, que[i].l, que[i].r, que[i].l, 1);
    }
    for (i = 1; i <= m; i++) printf("%lld\n", ans[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值