题目大意
给出一个长度为
n
n
n的数组
a
1
,
a
2
,
⋯
,
a
n
a_1,a_2,\cdots,a_n
a1,a2,⋯,an。
求有多少个区间
[
l
,
r
]
[l,r]
[l,r],满足
a
l
⨁
a
l
+
1
⨁
⋯
⨁
a
r
≥
k
a_l \bigoplus a_{l+1}\bigoplus\cdots\bigoplus a_r\ge k
al⨁al+1⨁⋯⨁ar≥k
时间限制
3s
数据范围
n
≤
1
0
6
n\le 10^6
n≤106
a
i
≤
1
0
9
a_i\le10^9
ai≤109
k
≤
1
0
9
k\le 10^9
k≤109
题解
对于异或的计算,很自然的想到字典树。
而异或与加法也有,可以通过前缀异或和来快速求出一个区间的异或和。
不妨记
a
a
a的前缀异或和为
s
s
s。
则对于一个右端点
s
r
s_r
sr,就需要在
s
0
,
s
1
,
⋯
,
s
r
−
1
s_0,s_1,\cdots,s_{r-1}
s0,s1,⋯,sr−1中寻找有多少个数与它的异或和大于等于
k
k
k。
于是这就是一个字典树的经典问题。
Code
//#pragma GCC optimize (2)
//#pragma G++ optimize (2)
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#include <vector>
#include <queue>
#define G getchar
#define ll long long
using namespace std;
int read()
{
char ch;
for(ch = G();(ch < '0' || ch > '9') && ch != '-';ch = G());
int n = 0 , w;
if (ch == '-')
{
w = -1;
ch = G();
} else w = 1;
for(;'0' <= ch && ch <= '9';ch = G())n = (n<<1)+(n<<3)+ch-48;
return n * w;
}
const int N = 1000006;
int n , tmp , k , s[N];
int ls[N * 31] , rs[N * 31] , g[N * 31] , tot;
int z[31] , S;
ll ans;
void ins (int s)
{
int x = 1;
for (int i = 29 ; i >= 0 ; i--)
{
g[x]++;
if (s & z[i])
{
if (rs[x] == 0)
{
tot++;
rs[x] = tot;
}
x = rs[x];
}
else
{
if (ls[x] == 0)
{
tot++;
ls[x] = tot;
}
x = ls[x];
}
}
g[x]++;
}
void work(int x , int y , int s)
{
if (x == 0) return;
if (s >= k)
{
ans = ans + g[x];
return;
}
if (y < 0) return;
if (s + z[y + 1] - 1 < k) return;
work(ls[x] , y - 1 , s | (S & z[y]));
work(rs[x] , y - 1 , s | ((S & z[y]) ^ z[y] ));
}
int main()
{
//freopen("f.in","r",stdin);
//freopen("f.out","w",stdout);
z[0] = 1;
for (int i = 1 ; i < 31 ; i ++)
z[i] = z[i - 1] * 2;
n = read();
k = read();
tot = 1;
ins(0);
for (int i = 1 ; i <= n ; i++)
{
tmp = read();
s[i] = s[i - 1] ^ tmp;
S = s[i];
work(1 , 29 , 0);
ins(s[i]);
}
printf("%lld\n", ans);
return 0;
}