[BZOJ3236][Ahoi2013]作业(莫队+树状数组)

本文介绍了一种使用莫队算法结合树状数组解决二维区间查询问题的方法。该算法适用于位置和权值有限制的问题,并能高效处理大量查询请求。文章详细解释了算法原理及其具体实现过程。

此题的询问是一个位置和权值都有限制的二维区间,但是题目具备无修改和允许离线两个条件,可以用莫队算法解决。
一个想法是:用莫队维护位置,树状数组维护权值。
具体的说,用一个数组cnt,维护莫队当前到达的位置区间内每个值的出现次数,并用两个树状数组,一个维护cnt的前缀和,另一个维护对于所有的j[1,i]cnt[j]>0的个数。
莫队移动指针时,以位置区间[l,r]转移到[l,r+1]为例:
记位置r+1上的值为x,则cnt[x]加一,并且在第一个树状数组的第x个位置加1。此时,还要判断如果现在cnt[x]=1,则第二个树状数组的第x个位置也要加1
这样,第一个树状数组的区间[a,b]之和就是第一问的结果,第二个树状数组的区间[a,b]之和就是第二问的结果。
复杂度O((m+n)nlogn。此外,还有一种O((m+n)n)的神奇的分块做法……
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5, M = 1e6 + 5;
int n, m, sq, a[N], S[N], T[N], cnt[N], res1[M], res2[M];
struct cyx {int id, l, r, a, b, bl;} que[M];
bool comp(cyx a, cyx b) {
    if (a.bl != b.bl) return a.bl < b.bl;
    return a.r < b.r;
}
int lowbit(int x) {return x & -x;}
void changeS(int x, int v) {
    for (; x <= n; x += lowbit(x))
        S[x] += v;
}
int askS(int x) {
    int res = 0;
    for (; x; x -= lowbit(x))
        res += S[x];
    return res;
}
void changeT(int x, int v) {
    for (; x <= n; x += lowbit(x))
        T[x] += v;
}
int askT(int x) {
    int res = 0;
    for (; x; x -= lowbit(x))
        res += T[x];
    return res;
}
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
int main() {
    int i; n = read(); m = read(); sq = sqrt(n);
    for (i = 1; i <= n; i++) a[i] = read();
    for (i = 1; i <= m; i++) {
        que[i].l = read(); que[i].r = read();
        que[i].a = read(); que[i].b = read();
        que[i].id = i; que[i].bl = (que[i].l - 1) / sq + 1;
    }
    sort(que + 1, que + m + 1, comp);
    for (i = que[1].l; i <= que[1].r; i++) {
        changeS(a[i], 1); cnt[a[i]]++;
        if (cnt[a[i]] == 1) changeT(a[i], 1);
    }
    res1[que[1].id] = askS(que[1].b) - askS(que[1].a - 1);
    res2[que[1].id] = askT(que[1].b) - askT(que[1].a - 1);
    for (i = 2; i <= m; i++) {
        int tl = que[i - 1].l, tr = que[i - 1].r,
        vl = que[i].l, vr = que[i].r;
        while (tl > vl) {
            changeS(a[--tl], 1); cnt[a[tl]]++;
            if (cnt[a[tl]] == 1) changeT(a[tl], 1);
        }
        while (tr < vr) {
            changeS(a[++tr], 1); cnt[a[tr]]++;
            if (cnt[a[tr]] == 1) changeT(a[tr], 1);
        }
        while (tl < vl) {
            changeS(a[tl++], -1); cnt[a[tl - 1]]--;
            if (!cnt[a[tl - 1]]) changeT(a[tl - 1], -1);
        }
        while (tr > vr) {
            changeS(a[tr--], -1); cnt[a[tr + 1]]--;
            if (!cnt[a[tr + 1]]) changeT(a[tr + 1], -1);
        }
        res1[que[i].id] = askS(que[i].b) - askS(que[i].a - 1);
        res2[que[i].id] = askT(que[i].b) - askT(que[i].a - 1);
    }
    for (i = 1; i <= m; i++) printf("%d %d\n", res1[i], res2[i]);
    fclose(stdin); fclose(stdout);
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值