题目链接: Beautiful Subarrays
大致题意
给定一个长度为n的序列 a a a.
问: 有多少个连续的子序列满足其内部所有元素异或的结果大于等于 k k k.
解题思路
思维
首先我们考虑对于这种询问区间个数的问题, 我们暴力枚举左右端点的复杂度一定是爆炸的.
我们通常会采取一种枚举右端点的方式来优化复杂度.
对于本题而言, 我们的做法是: 枚举当右端点为r时, 找出有多少个左端点l符合要求.
考虑到异或满足前缀和的性质, 不妨记序列 s s s为 a a a的前缀异或数组. 则我们求[l, r]的异或结果, 等价于计算 s r ⊕ s l − 1 s_r ⊕ s_{l - 1} sr⊕sl−1.
我们考虑按二进制位从高到低枚举
k
k
k, 有两种情况:
①
k
k
k这位为0, 则如果
s
r
⊕
s
l
−
1
s_r ⊕ s_{l - 1}
sr⊕sl−1的这位结果为1, 则一定满足要求, 计入结果即可.
②
k
k
k这位为1, 则我们需要继续向后枚举.
最后再加上异或结果等于 k k k的贡献.
01Trie
通过上面分析, 当我们固定了 s r s_r sr后, 我们需要统计有多少个 s l − 1 s_{l - 1} sl−1满足要求. 因此我们不妨通过01Trie来存储前缀和信息.
我们在树中维护每个节点的数字个数即可.
代码细节: 由于我们是要找对应的 s l − 1 s_{l - 1} sl−1, 我们选择空的前缀也是一种合法情况(相当于选择区间[1, r]). 因此我们需要往树种插入 0 0 0.
AC代码
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 1E6 + 10, B = 31, M = N * B;
int n, m;
int t[M][2], ind;
int cou[M]; //每个节点的数字个数
void insert(int c) {
int x = 0;
for (int i = B - 1; i >= 0; --i) {
int w = c >> i & 1;
if (!t[x][w]) t[x][w] = ++ind;
x = t[x][w], cou[x]++;
}
}
int ask(int c) {
int x = 0; int res = 0;
for (int i = B - 1; i >= 0; --i) {
int w = c >> i & 1;
if (m >> i & 1) {
if (!t[x][w ^ 1]) return res;
else x = t[x][w ^ 1];
}
else {
res += cou[t[x][w ^ 1]];
x = t[x][w];
if (!x) return res;
}
}
res += cou[x]; //结果等于k的
return res;
}
int main()
{
cin >> n >> m;
insert(0); //记得推入0.
ll res = 0; int sum = 0;
rep(i, n) {
int x; scanf("%d", &x);
sum ^= x;
res += ask(sum);
insert(sum);
}
cout << res << endl;
return 0;
}