CF 617E XOR and Favorite Number 异或和为k的区间个数 莫队

该博客介绍了如何利用莫队算法高效解决数组中异或值为k的区间个数查询问题。通过计算前缀异或和,并维护每个前缀和出现的次数,可以实现对新元素的O(1)更新和查询,从而在1e5次询问下高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

给出一个长度1e5的数组, 1e5次询问, l l l r r r之间异或值为 k k k的区间个数.
这个题的思想和那个"求区间和为0的最长区间长度"的题很像.
a ∧ b = c a \wedge b=c ab=c, 那么 a ∧ c = b a \wedge c=b ac=b.
先对a数组计算前缀异或和sum.
∧ ( a [ l ] . . . a [ r ] ) = k \wedge(a[l]...a[r])=k (a[l]...a[r])=k, 即 s u m [ r ] ∧ s u m [ l − 1 ] = k sum[r]\wedge sum[l-1]=k sum[r]sum[l1]=k.
假如当前已经计算出区间 i i i j j j中, 异或值为 k k k的区间个数 a n s ans ans.
现在我们要新增一个数 j + 1 j+1 j+1.
新增一个数答案不可能更坏, 也就是说新的 a n s ans ans是在当前 a n s ans ans上加上 j + 1 j+1 j+1的贡献.
那么 j + 1 j+1 j+1的贡献怎么算? 其实就是 i i i j j j之间的数, 有多少个会和 j + 1 j+1 j+1"配对"(满足题意 ∧ ( a [ x ] . . . a [ j + 1 ] ) = k \wedge (a[x]...a[j+1])=k (a[x]...a[j+1])=k).
s u m [ x ] ∧ s u m [ j + 1 ] = k sum[x]\wedge sum[j+1]=k sum[x]sum[j+1]=k;
s u m [ j + 1 ] ∧ k = s u m [ x ] sum[j+1]\wedge k=sum[x] sum[j+1]k=sum[x].
这是什么意思呢? 对于 i i i j j j里任何一个 x x x, 只需要 s u m [ x ] = s u m [ j + 1 ] ∧ k sum[x]=sum[j+1]\wedge k sum[x]=sum[j+1]k, 那么它就能和 j + 1 j+1 j+1配对!
那么我们自然想到维护 i i i j j j内,每个 s u m sum sum值的出现的次数. 这样在每来一个新的 j + 1 j+1 j+1, 我们就能 O ( 1 ) O(1) O(1)算出它的贡献.
如果要从区间中去掉头/尾, 其实道理和新增是一样的.
显然就是莫队了.
p.s.
因为询问的是l到r的区间内有多少个子区间异或和为k, 而我们真正的操作是查询l-1到r之间的前缀异或和, 所以l要-1.
代码:

const int M = 2123456;// 2e5+5
int a[M], n, q, k, blo[M], bloSZ;
ll answer, ans[M];
struct query {
    int l, r, id;
} queries[M];

bool cmp(query x, query y) {return (blo[x.l] ^ blo[y.l]) ? blo[x.l] < blo[y.l] : ((blo[x.l] & 1) ? x.r < y.r : x.r > y.r);}

void divide() {
    bloSZ = sqrt(n);
    for (int i = 1; i <= n; ++i) {
        blo[i] = (i - 1) / bloSZ + 1;
    }
}

ll cnt[M];

void add(int pos) {
    answer += cnt[a[pos] ^ k];
    cnt[a[pos]]++;
}

void del(int pos) {
    cnt[a[pos]]--;
    answer -= cnt[a[pos] ^ k];
}

void Mo() {
    sort(queries + 1, queries + 1 + q, cmp);
    int l = 0, r = -1;
    for (int i = 1; i <= q; ++i) {
        while (l > queries[i].l)add(--l);
        while (r < queries[i].r)add(++r);
        while (l < queries[i].l)del(l++);
        while (r > queries[i].r)del(r--);
        ans[queries[i].id] = answer;
    }
}


void init() {
    n = read(), q = read(), k = read();
    for (int i = 1; i <= n; ++i) {
        a[i] = read();
        a[i] = a[i - 1] ^ a[i];
    }
    divide();
    for (int i = 1; i <= q; ++i) {
        int l = read(), r = read();
        l--;
        queries[i] = {l, r, i};
    }
    Mo();
    for (int i = 1; i <= q; ++i)write(ans[i]), enter;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值