I love counting HDU - 6964((莫队+分块)||(莫队+树状数组))

题目:https://acm.hdu.edu.cn/showproblem.php?pid=6964

题意:题意非常好理解,给一个数组c,然后q个询问,每次询问,给一个l,r,a,b,要求求出在l-r区间内,a^c<=b的c的种类个数。

题解1:莫队+树状数组
详细思路看这里,代码中翻译也很仔细。

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 200010;
int n, m, a[N], k, cnt[N], c[N], ans[N];
// a是题目输入的数组,k是莫队中分块的区间,cnt是莫队中记录每个数的个数,c是树状数组中的数组
// ans来记录每个询问的答案
struct query {
    int l, r, a, b, id;
    bool operator<(const query &b) const {  
        if (l / k == b.l / k) return r < b.r;
        return l < b.l;
    }
} q[N];
//树状数组板子,用来统计i前面的数出现了几个包括i
void add_su(int x, int y) {  
    for (; x <= N; x += x & -x) c[x] += y;
}
int ask(int x) {
    int ss = 0;
    for (; x; x -= (x & (-x))) {
        if (x <= 0) break;
        ss += c[x];
    }
    return ss;
}
void add(int x) {
    //如果这个数第一次出现,就加入到数组中
    if (++cnt[x] == 1) add_su(x, 1);
}
void sub(int x) {
    //这个数没了,就剔除
    if (--cnt[x] == 0) add_su(x, -1);
}
int main() {
    scanf("%d", &n);
    k = sqrt(n);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    scanf("%d", &m);
    for (int i = 0; i < m; i++) {
        scanf("%d%d%d%d", &q[i].l, &q[i].r, &q[i].a, &q[i].b);
        q[i].id = i;
    }
    sort(q, q + m);
    int l = 1, r = 0;
    for (int i = 0; i < m; i++) {
        while (l > q[i].l) add(a[--l]);
        while (l < q[i].l) sub(a[l++]);
        while (r < q[i].r) add(a[++r]);
        while (r > q[i].r) sub(a[r--]);
        //上面是莫队常规操作
        int s = 0;
        int a = q[i].a, b = q[i].b;
        for (int j = 19; j >= 0; j--) {
            int p = s;//这里p等于s,是因为有时候s不需要变,但为了方便计算,引入一个p
            if (b >> j & 1) {//如果b第j位为1
                if (a >> j & 1)//如果a第j位为1
                //那么如果我的c第j位为1,则a和c异或,这一位为0,假如现在j=3则1000到1111所有的
                //数都是满足a^c<b的,当然前面的高位还要满足a^c=b的情况
                    p |= 1 << j;//给s补上这一位,用p表示,因为s只走在这一位a^c等于b的情况,这样下面才有可能出现a^c<b
                else//如果a的第j为0,那么我只能走c的第j位为1的情况,然后异或值这一位是1和b相等,
                    //这个时候p的这一位还是0,所以如果我有0000到0111之间的数,肯定在这一位a^c<b的,所以这里p不用动,默认为0就好
                    s |= 1 << j;//给s这一位补上1
                //ask寻找这俩数之间有多少个数                 //p-1是因为要包含p,但是因为这是树状数组,所以要往前面找一位
                ans[q[i].id] += ask(p + (1 << j) - 1) - ask(p - 1);
            } else {//如果b这一位为0,在这一位不可能有a^c<b
                //如果a这一位为1,我就走1的方向,给s补上1,如果a这一位为0,我的s也要走0的方向,默认为0就不用动
                if (a >> j & 1) s |= 1 << j;
            }
        }
        //上面的操作都是找小于的情况,这里等于的情况特判就行
        //a^c=b,即是c=a^b,特判一下是否存在这样的c,因为cnt可能记录很多个,所以取1就行
        ans[q[i].id] += cnt[q[i].a ^ q[i].b] ? min(cnt[q[i].a ^ q[i].b], 1) : 0;
    }
    for (int i = 0; i < m; i++) printf("%d\n", ans[i]);
    return 0;
}

莫队+分块:和树状数组相比,只是把统计不同数的过程,换成了分块。

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int N = 100010;
int n, m, a[N], k, cnt[N], sum[N], c[2 * N], ans[N];
struct query {
    int l, r, a, b, id;
    bool operator<(const query &b) const {
        if (l / k == b.l / k) return r < b.r;
        return l < b.l;
    }
} q[N];
void add(int x) {
    //看这个数属于哪个块
    if (++cnt[x] == 1) sum[x / k]++, c[x]++;
}
void sub(int x) {
    if (--cnt[x] == 0) sum[x / k]--, c[x]--;
}
int ask(int x) {
    int res = 0;
    //第一个for循环,暴力找最后一个块
    for (int i = x / k * k; i <= x; i++) res += c[i];
    //然后遍历前面每一个块,看看有多个数
    for (int i = 0; i < x / k; i++) res += sum[i];
    return res;
}
int main() {
    scanf("%d", &n);
    k = sqrt(n);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    scanf("%d", &m);
    for (int i = 0; i < m; i++) {
        scanf("%d%d%d%d", &q[i].l, &q[i].r, &q[i].a, &q[i].b);
        q[i].id = i;
    }
    sort(q, q + m);
    int l = 1, r = 0;
    for (int i = 0; i < m; i++) {
        while (l > q[i].l) add(a[--l]);
        while (l < q[i].l) sub(a[l++]);
        while (r < q[i].r) add(a[++r]);
        while (r > q[i].r) sub(a[r--]);
        int s = 0;
        int a = q[i].a, b = q[i].b;
        for (int j = 19; j >= 0; j--) {//这里和树状数组写法一样
            int p = s;
            if (b >> j & 1) {
                if (a >> j & 1)
                    p |= 1 << j;
                else
                    s |= 1 << j;
                ans[q[i].id] += ask(p + (1 << j) - 1) - ask(p - 1);
            } else {
                if (a >> j & 1) s |= 1 << j;
            }
        }//这里和树状数组相比,只是把树状数组查找换成了分块查找
        ans[q[i].id] += c[q[i].a ^ q[i].b];
    }
    for (int i = 0; i < m; i++) printf("%d\n", ans[i]);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值