题目: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;
}