[BZOJ3956]Count

题目链接BZOJ3956

题目大意
给出一个序列,若干个询问,问在区间 [l..r] 内,有多少个无序数对(i, j),满足 k(i,j),val[k]<min(val[i],val[j]) ;若 (i,j)= ,也视为一对合法点对。

分析
1. 有一个非常简单的暴力算法:对于每个询问,枚举区间内的左右端点,然后扫一遍判定是否合法,复杂度 O(MN3)
2.1 上一个算法可以怎么优化?可以先通过 O(N) 的单调栈预处理出的第 i 个点延伸到的最右端点为Right[i] val[i]val[Right[i]] Left[i] 同理。
2.2 从而 O(1) 判断每个点对是否合法,这样的话就降到了 O(MN2)
3.1 经过上面的一个算法,若我们只考虑以第i个点为点对中高度较低的点,那么这样的合法点对至多只有两个: (i,Right[i]) (Left[i],i)
3.2 这样的话,我们可以只枚举高度较低点,再稍微去一下重就好了,复杂度 O(MN)
4.1 现在,算法的瓶颈在于,是否能不进行区间内的枚举,而得出答案呢。我们把这些点对映射到线段树上:若以第 i 个点为较低点,能有一个合法点对的另一个端点在j处,那么在第 i 棵线段树上的j位置+1。
4.2 然后主席树求 T[r]T[l1] 的区间 [l..r] 的和就好了,时间 O(NlogN) ,空间 O(NlogN)
5. 其实能像这样的一步一步的想题还是蛮有趣的。

上代码

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

const int N = 3e5 + 10;
const int M = 3e7 + 10;
const int INF = 0x3f3f3f3f;

int n, m, type;
inline int read() {
    char ch;
    int ans = 0, neg = 1;
    while (!isdigit(ch = getchar()))
        if (ch == '-') neg = -1;
    while (isdigit(ch))
        ans = ans * 10 + ch - '0', ch = getchar();
    return ans * neg;
}

int T[N], cnt;
int lc[M], rc[M], tot[M];
int modify(int a, int l, int r, int p, int c) {
    int now = ++cnt;
    tot[now] = tot[a] + c;
    if (l == r) return now;
    int mid = (l + r) >> 1;
    if (p <= mid) rc[now] = rc[a], lc[now] = modify(lc[a], l, mid, p, c);
    else lc[now] = lc[a], rc[now] = modify(rc[a], mid + 1, r, p, c);
    return now;
}
int query(int a, int b, int l, int r, int ll, int rr) {
    if (l == ll && r == rr) return tot[a] - tot[b];
    int mid = (l + r) >> 1;
    if (rr <= mid) return query(lc[a], lc[b], l, mid, ll, rr);
    else if (ll > mid) return query(rc[a], rc[b], mid + 1, r, ll, rr);
    return query(lc[a], lc[b], l, mid, ll, mid) + query(rc[a], rc[b], mid + 1, r, mid + 1, rr);
}

int val[N], lef[N], rig[N];
void calcLef() {
    stack <int> S;
    for (int i = n; i >= 1; i--) {
        while (!S.empty() && val[i] >= val[S.top()])
            lef[S.top()] = i, S.pop();
        S.push(i);
    }
    while (!S.empty()) lef[S.top()] = 0, S.pop();
}
void calcRig() {
    stack <int> S;
    for (int i = 1; i <= n; i++) {
        while (!S.empty() && val[i] >= val[S.top()])
            rig[S.top()] = i, S.pop();
        S.push(i);
    }
    while (!S.empty()) rig[S.top()] = n + 1, S.pop();
}
void init() {
    n = read(), m = read(), type = read();
    for (int i = 1; i <= n; i++) val[i] = read();
    val[0] = -INF, val[n + 1] = -INF, calcLef(), calcRig();
    for (int i = 1; i <= n; i++) {
        T[i] = T[i - 1];
        if (val[lef[i]] > val[i]) T[i] = modify(T[i], 1, n, lef[i], 1);
        if (val[rig[i]] >= val[i]) T[i] = modify(T[i], 1, n, rig[i], 1);
    }
}
void figure() {
    int last = 0;
    for (int i = 1; i <= m; i++) {
        int l = read(), r = read();
        if (type) l = (l + last - 1) % n + 1, r = (r + last - 1) % n + 1;
        if (l > r) swap(l, r);
        printf("%d\n", last = query(T[r], T[l - 1], 1, n, l, r));
    }
}

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

}

以上

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值