[BZOJ4540]序列

题目链接BZOJ4540

题目大意
给定一个序列,有若干个询问,问某个连续子序列的所有连续子序列的最小值的和,即 lijrmin[i..j]

分析
1. 首先一看这道题,长得就非常的“莫队”,那么我们该怎么转移呢?
2. 若我们已经得到了区间 [l..r1] 的答案,那么对于新加入的元素 r ,我们只考虑包含r的所有后缀对答案做出的贡献,也就是 S=ri=lmin[i..r]
3. 设 pre[i] i 左边第一个比i小的元素的位置,可以用单调栈 O(n) 求出;设 sum[r]=val[r]×(rpre[r])+val[pre[r]]×(pre[r]pre[pre[r]]) +... 直到 r=0
4. 那么 sum[r] S 有什么关系呢?S“含于” sum[r] 的;那么怎么求出 S 呢?
5. 设min[l..r]元素所在位置为 p ,可以用ST表O(1)求出,则 S=val[p]×(pl+1)+sum[r]sum[p] ,即以 p 为最小值的部分的答案加上不以p为最小值的部分的答案。
6. 那么对于其他三种情况,对称的做一下就好了。

上代码

#include <stack>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

typedef long long LL;
const int M = 20;
const int SQR = 320;
const int N = 1e5 + 10;

int n, m;
inline int read() {
    char ch;
    int ans = 0, neg = 1;
    while (ch = getchar(), ch < '0' || ch > '9')
        if (ch == '-') neg = -1;
    while (ch >= '0' && ch <= '9')
        ans = ans * 10 + ch - '0', ch = getchar();
    return ans * neg;
}

struct nodeQry {
    int l, r, bl, id;
    inline void input(int a) {
        l = read(), r = read(), bl = l / SQR, id = a;
    }
    inline bool operator < (const nodeQry &a) const {
        return bl != a.bl ? bl < a.bl : r < a.r;
    }
} qry[N];

// nxt和suml就是pre和sumr的对称情况
int val[N], pre[N], nxt[N];
int minp[N][M];
LL suml[N], sumr[N];
#define p1 minp[i][j - 1]
#define p2 minp[k][j - 1]
void calcST() { //ST表
    for (int i = 1; i <= n; i++) minp[i][0] = i;
    for (int j = 1; (1 << j) <= n; j++)
        for (int i = 1; i <= n; i++) {
            int k = i + (1 << (j - 1));
            if (k > n) minp[i][j] = minp[i][j - 1];
            else minp[i][j] = (val[p1] <= val[p2] ? p1 : p2);
        }
}
#undef p1
#undef p2
void calcPN() {
    stack <int> S;
    for (int i = 1; i <= n; i++) {
        while (!S.empty() && val[S.top()] > val[i])
            nxt[S.top()] = i, S.pop();
        S.push(i);
    }
    while (!S.empty()) nxt[S.top()] = n + 1, S.pop();
    for (int i = n; i >= 1; i--) {
        while (!S.empty() && val[S.top()] > val[i])
            pre[S.top()] = i, S.pop();
        S.push(i);
    }
    while (!S.empty()) pre[S.top()] = 0, S.pop();
}
void calcSum() {
    for (int i = 1; i <= n; i++)
        sumr[i] = sumr[pre[i]] + (LL)val[i] * (i - pre[i]);
    for (int i = n; i >= 1; i--)
        suml[i] = suml[nxt[i]] + (LL)val[i] * (nxt[i] - i);
}
void init() {
    n = read(), m = read();
    for (int i = 1; i <= n; i++) val[i] = read();
    for (int i = 1; i <= m; i++) qry[i].input(i);
    calcST(), calcPN(), calcSum();
    sort(qry + 1, qry + m + 1);
}

struct nodeAns {
    int id; LL cnt;
    nodeAns() {}
    nodeAns(int a, LL b) : id(a), cnt(b) {}
    inline bool operator < (const nodeAns &a) const {
        return id < a.id;
    }
} ans[N];
LL tot;
#define p1 minp[l][k]
#define p2 minp[r - (1 << k) + 1][k]
int len[N];
void calcK() {
    int k = 0;
    for (int i = 1; i <= n; i++) {
        while (1 << (k + 1) <= i) k++;
        len[i] = k;
    }
}
inline int getMin(int l, int r) {
    int k = len[r - l + 1];
    return (val[p1] <= val[p2] ? p1 : p2);
}
void figure() {
    calcK();
    int l = qry[1].l, r = l, p = minp[l][0];
    tot = (LL)val[p];
    for (int i = 1; i <= m; i++) {
        while (r < qry[i].r) {
            p = getMin(l, ++r);
            tot += sumr[r] - sumr[p] + (LL)val[p] * (p - l + 1);
        }
        while (l > qry[i].l) {
            p = getMin(--l, r);
            tot += suml[l] - suml[p] + (LL)val[p] * (r - p + 1);
        }
        while (r > qry[i].r) {
            tot -= sumr[r] - sumr[p] + (LL)val[p] * (p - l + 1);
            p = getMin(l, --r);
        }
        while (l < qry[i].l) {
            tot -= suml[l] - suml[p] + (LL)val[p] * (r - p + 1);
            p = getMin(++l, r);
        }
        ans[i] = nodeAns(qry[i].id, tot);
    }
    sort(ans + 1, ans + m + 1);
    for (int i = 1; i <= m; i++)
        printf("%lld\n", ans[i].cnt);
}

int main() {
    init();
    figure();
    return 0;
}

以上

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值