[HNOI2017]影魔(扫描线,树状数组)

本文介绍了一种解决区间贡献查询问题的高效算法。该算法通过预处理数组获取特定位置的边界信息,并采用离线处理结合扫描线及树状数组的方法来快速求解区间贡献总和。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

给定排列aa,如果区间(l,r)满足max(al+1,al+2,...,ar1)<almax(al+1,al+2,...,ar−1)<almax(al+1,al+2,...,ar1)armax(al+1,al+2,...,ar−1)⩽ar,那么将产生p1p1的贡献否则如果只满足一个条件将产生p2p2的贡献。

多组询问,每次询问一个区间[L,R][L,R]求满足[l,r][L,R][l,r]∈[L,R]的贡献和。

Solution

先预处理一个数aiai左边第一个大于它的位置lili,和右边第一个大于它的位置riri

  • 则区间[li,ri][li,ri]产生p1p1的贡献。可以抽象为一个点(li,ri)(li,ri)
  • 若区间左端点为lili,右端点位于(i,rI)(i,rI),可以产生p2p2的贡献。抽象为线段(li,i+1..rl1)(li,i+1..rl−1)
  • 若区间左端点位于(li,i)(li,i),右端点位于rIrI,可以产生p2p2的贡献。抽象为线段(li1...i1,rl)(li−1...i−1,rl)

而一个询问区间[L,R][L,R]则可以抽象为一个左下角为[L,L][L,L]右上角为[R,R][R,R]的矩形。

然后离线+扫描线+树状数组即可。

(ps.这里用了一个小技巧:把线段(li,i+1..rl1)(li,i+1..rl−1)映射为线段(i+1..rl1,li)(i+1..rl−1,li)。这样更好维护)

#include <bits/stdc++.h>
using namespace std;

typedef long long lint;
const int maxn = 200005;

int n, m, p1, p2, a[maxn], le[maxn], ri[maxn], stk[maxn], top;

inline int gi()
{
    char c = getchar();
    while (c < '0' || c > '9') c = getchar();
    int sum = 0;
    while ('0' <= c && c <= '9') sum = sum * 10 + c - 48, c = getchar();
    return sum;
}

struct rectangle {
    int l, r, y, Id, val;
    bool operator < (const rectangle &a) const {
        return y < a.y;
    }
}s1[maxn * 2], s2[maxn * 3];


#define lowbit(x) ((x) & (-x))
lint ans[maxn], sum1[maxn], sum2[maxn];
inline void add(int x, int a)
{
    int t = x;
    while (x <= n) {
        sum1[x] += a; sum2[x] += (lint)t * a;
        x += lowbit(x);
    }
}

inline lint query(int x)
{
    lint t = x, res = 0;
    while (x >= 1) {
        res += (t + 1) * sum1[x] - sum2[x];
        x -= lowbit(x);
    }
    return res;
}

int main()
{
    n = gi(); m = gi(); p1 = gi(); p2 = gi();
    a[0] = a[n + 1] = n + 1;
    for (int i = 0; i <= n + 1; ++i) {
        if (1 <= i && i <= n) a[i] = gi();
        while (top && a[stk[top]] < a[i]) ri[stk[top--]] = i;
        le[i] = stk[top];
        stk[++top] = i;
    }

    int tot1 = 0;
    for (int l, r, i = 1; i <= m; ++i) {
        l = gi(); r = gi(); ans[i] += (r - l) * p1;
        if (l > 1) s1[++tot1] = (rectangle) {l, r, l - 1, i, -1};
        s1[++tot1] = (rectangle) {l, r, r, i, 1};
    }
    sort(s1, s1 + tot1 + 1);

    int tot2 = 0;
    for (int i = 1; i <= n; ++i) {
        if (1 <= le[i] && ri[i] <= n) s2[++tot2] = (rectangle) {le[i], le[i], ri[i], 0, p1};
        if (le[i] + 1 <= i - 1 && ri[i] <= n) s2[++tot2] = (rectangle) {le[i] + 1, i - 1, ri[i], 0, p2};
        if (1 <= le[i] && i + 1 <= ri[i] - 1) s2[++tot2] = (rectangle) {i + 1, ri[i] - 1, le[i], 0, p2};
    }
    sort(s2, s2 + tot2 + 1);

    int k1 = 1, k2 = 1;
    for (int i = 1; i <= n && k1 <= tot1; ++i) {
        while (k2 <= tot2 && s2[k2].y == i)
            add(s2[k2].l, s2[k2].val), add(s2[k2].r + 1, -s2[k2].val), ++k2;
        while (k1 <= tot1 && s1[k1].y == i)
            ans[s1[k1].Id] += (query(s1[k1].r) - query(s1[k1].l - 1)) * s1[k1].val, ++k1;
    }

    for (int i = 1; i <= m; ++i) printf("%lld\n", ans[i]);

    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值