【前缀异或和】【字典树】CF665E Beautiful Subarrays
【前缀异或和】【字典树】CF665E Beautiful Subarrays
前置知识
-
规定\(sum[l,r]=a_l\space xor \space a_{l+1}\space xor \space ...\space xor \space a_r\)
则有\(sum[l,r]=sum[1,r]\space xor \space sum[1,l-1]\)
原理:
- 一个数异或本身等于0
- 0异或任何数等于任何数本身。
-
字典树
- 将多个字符串存入到一棵树中,若存在两个字符串的前缀相同,则这两个字符串共用一个树干。
node[i][j]
代表标号为i的状态的下一个字母为j的状态的标号。- 利用一个计数器来给当前未存在的状态开辟一个编号。
- 状态1是一个根节点,它本身不包含信息。
题目思路
因为要求异或和大于k的区间,可以转换成一个前缀异或和异或上另外一个更为前面的前缀异或和的最后的结果大于k的情况的个数。
而对于一个数k来说,假设k=100110
我们只需要知道当前异或起来能产生的11xxxx,101xxx,100111这三种类型的总共的数字的个数为多少就行。
其他分析
\(0<=a_i<=1e9<2^{30}\)
所以我们可以将每一个数字看成是一个长度为30的01串
因而我们要按照30个位置进行处理一个数字,且每一个状态后继有两个位置0和1
而\(1<=n<=1e6\),因而最多只会被分配1e6*30的空间。
所以我们需要开的空间node[1e6*30][2]
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define rep(i,x,n) for(int i=x;i<n;i++)
#define repd(i,x,n) for(int i=x;i<=n;i++)
#define MAX 1000005
#define MOD 1000000007
using namespace std;
const int N = 1E6+500;
int n,k,tot = 1,sum[N*31],a[N],node[N*31][2];
int insert(int x)
{
int cur = 1;
for(int i=30;i>=0;i--)
{
int c = (x>>i) & 1;
if( !node[cur][c] ) node[cur][c] = ++tot;
cur = node[cur][c];
sum[ cur ]++;
}
}
int query(int x)
{
int cur = 1,res = 0;
for(int i=30;i>=0;i--)
{
int c = (k>>i) & 1;
int numx = (x>>i) & 1;
if(c==1) cur = node[ cur ][ !numx ];//没什么好说,找一个异或起来为1的
else
{
res += sum[ node[ cur ][ !numx ] ];//异或起来为1加入答案
cur = node[ cur ][ numx ];//异或起来为0(相同),进行进一步的探索
}
}
res += sum[cur];//最后要加上等于本身
return res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=2;i<=n;i++)
a[i] = a[i]^a[i-1];
ll ans = 0;
insert(0);
for(int i=1;i<=n;i++)
{
ans += query(a[i]);
insert(a[i]);
}
cout<<ans;
return 0;
}