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

Description

给定排列 a a ,如果区间 (l,r) ( l , r ) 满足 max(al+1,al+2,...,ar1)<al m a x ( a l + 1 , a l + 2 , . . . , a r − 1 ) < a l max(al+1,al+2,...,ar1)ar m a x ( a l + 1 , a l + 2 , . . . , a r − 1 ) ⩽ a r ,那么将产生 p1 p 1 的贡献否则如果只满足一个条件将产生 p2 p 2 的贡献。

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

Solution

先预处理一个数 ai a i 左边第一个大于它的位置 li l i ,和右边第一个大于它的位置 ri r i

  • 则区间 [li,ri] [ l i , r i ] 产生 p1 p 1 的贡献。可以抽象为一个点 (li,ri) ( l i , r i )
  • 若区间左端点为 li l i ,右端点位于 (i,rI) ( i , r I ) ,可以产生 p2 p 2 的贡献。抽象为线段 (li,i+1..rl1) ( l i , i + 1.. r l − 1 )
  • 若区间左端点位于 (li,i) ( l i , i ) ,右端点位于 rI r I ,可以产生 p2 p 2 的贡献。抽象为线段 (li1...i1,rl) ( l i − 1... i − 1 , r l )

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

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

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

#include 
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;
}

你可能感兴趣的:(文章类型——题解,数据结构——树状数组,算法——扫描线)