[HNOI 2017] bzoj4826 影魔 [单调栈+扫描线]

Description:
提供 p1 p 1 的攻击力: i,j i , j 位置的数是区间 [i,j] [ i , j ] 的最大值和次大值
提供 p2 p 2 的攻击力: i,j i , j 位置的数有一个是区间 [i,j] [ i , j ] 的最大值,另一个不是次大值


Solution:
考虑枚举 ai a i ,并用单调栈求出左右第一个比 ai a i 大的位置 li,ri l i , r i
p1 p 1 有贡献的区间是左端点为 li l i ,右端点为 ri r i
p2 p 2 有贡献的区间是左端点为 li l i ,右端点为 (i,ri) ( i , r i ) ,右端点为 ri r i ,左端点为 (li,i) ( l i , i )
这样我们就可以扫描线+树状数组了。


#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 4e5 + 5;
int n, m, p1, p2, tot, top = 1;
ll t1[N], t2[N] , ans[N];
int a[N], st[N], l[N], r[N];
struct seg {
    int l, r, p, id, v;
    seg() {}
    seg(int _l, int _r, int _p, int _id, int _v) : l(_l), r(_r), p(_p), id(_id), v(_v) {}
    bool friend operator < (const seg &a, const seg &b) {
        return a.p < b.p;
    }
} s[N], q[N];
void update(int x, int d) {
    for(int i = x; i <= n; i += i & -i) {
        t1[i] += d;
        t2[i] += x * d;
    }
}
ll query(int x) {
    ll ret = 0;
    for(int i = x; i; i -= i & -i) {
        ret += (ll)(x + 1) * t1[i] - t2[i];
    }
    return ret;
}
int main() {
    scanf("%d%d%d%d", &n, &m, &p1, &p2);
    a[0] = a[n + 1] = n + 1;
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        while(top && a[i] > a[st[top]]) {
            r[st[top]] = i;
            --top;
        }
        l[i] = st[top];
        st[++top] = i;
    }
    for(int i = 1; i <= m; ++i) {
        int l, r;
        scanf("%d%d", &l, &r);
        ans[i] += (r - l) * p1;
        q[i] = seg(l, r, l - 1, i, -1);
        q[i + m] = seg(l, r, r, i, 1);
    }
    sort(q + 1, q + 2 * m + 1);
    while(top) {
        r[st[top--]] = n + 1;
    }
    for(int i = 1; i <= n; ++i) {
        if(l[i] && r[i] <= n) {
            s[++tot] = seg(l[i], l[i], r[i], 0, p1);
        }
        if(r[i] <= n && l[i] + 1 <= i - 1) {
            s[++tot] = seg(l[i] + 1, i - 1, r[i], 0, p2);
        }
        if(l[i] && i + 1 <= r[i] - 1) {
            s[++tot] = seg(i + 1, r[i] - 1, l[i], 0, p2);
        }
    }
    sort(s + 1, s + tot + 1);
    for(int i = 1, p = 1, j = 1; i <= n; ++i) {
        while(j <= tot && s[j].p == i) {
            update(s[j].l, s[j].v);
            update(s[j].r + 1, -s[j].v);
            ++j;
        }
        while(p <= 2 * m && !q[p].p) {
            ++p;
        }
        while(p <= 2 * m && q[p].p == i) {
            ans[q[p].id] += q[p].v * (query(q[p].r) - query(q[p].l - 1)); 
            ++p;
        }
    }
    for(int 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、付费专栏及课程。

余额充值