10月17日备战Noip2018模拟赛15(A组)
T1 Water 淘淘的大水题
题目描述
淘淘在坐高铁回家的路上看到了一道很水的题:
给出一个正整数序列a1,a2,...,an。定义一个序列的子序列Subarray(L,R),为aL, aL+1, ..., aR,则这个子序列的xor和为aL xor aL+1 xor ... xor aR,其中1≤L≤R≤n。现在淘淘想知道,有多少个子序列的xor和不小于给定的正整数K。
由于淘淘一上高铁就无聊的想睡觉,于是就把这个问题交给你了
输入格式
第一行,一个整数T(T≤5),表示数据组数。
从第二行开始,第2T行包含两个整数n, K;
n为正整数序列中元素个数,K的含义如上所述。第2T+1行包含n个正整数a1,a2,...,an,即正整数序列中的元素。
输出格式
共T行,每行一个正整数,表示有多少个子序列xor和不小于给定的正整数K
输入样例
3
3 1
1 2 3
3 2
1 2 3
3 3
1 2 3
输出样例
5
3
2
数据范围
对于20%的数据,0<n≤2000;
对于100%的数据,0<n≤1000000, 0≤K≤109, 0≤ai≤109
思路
Trie
看到xor, 就很容易想到01字典树了,用preSum来记录前缀异或和, 用cnt[]来记录每个节点维护多少数的前缀的异或和, 那么对于一个前缀和preSum, 从高位到地位, 逐位与K比较, 这个过程在Trie上进行, 如果preSum 的第i位与k的第i位相同, 继续走这个节点的左分支与k比较,第i位变成 0; 如果preSum的第i位变成了1 , 那么不论后面的数是多少,preSum都是大于k的, 此时就直接把这个节点的右子树cnt[]中维护的信息累加入答案
代码
#include <iostream>
#include <cstdio>
#include <cctype>
#include <cstring>
using namespace std;
const int MAXN = 1e7 + 5;
int M, n, k, a, x, tot, preSum;
int T[MAXN][2], cnt[MAXN]; //T[][]是Tire, cnt[]用来记录每个节点维护了多少的前缀和
long long ans;
inline int read ();
inline void insert(int x);
inline void search (int x);
int main ()
{
freopen ("water.in", "r", stdin);
freopen ("water.out", "w", stdout);
M = read ();
while (M --){
memset (cnt, 0, sizeof (cnt));
n = read (), k = read ();
ans = 0, tot = 1, preSum = 0;
T[1][0] = T[1][1] = 0;
insert (0);
for (register int i = 1; i <= n; ++ i){
a = read ();
preSum ^= a; //处理前缀xor和
search (preSum); // 比较
insert (preSum);
}
printf ("%lld\n", ans);
}
fclose (stdin);
fclose (stdout);
return 0;
}
inline int read ()
{
char ch = getchar ();
while (!isdigit (ch)) ch = getchar ();
int x = 0;
while (isdigit (ch)) {
x = x * 10 + ch - '0';
ch = getchar ();
}
return x;
}
inline void insert(int x) // 将当前的前缀xor和化为31位的二进制串,加入Trie
{
int u, v;
u = 1;
for (int i = 1 << 30; i; i >>= 1){
v = (x & i) ? 1 : 0;
if (!T[u][v]) {
++ tot;
T[u][v] = tot;
cnt[tot] = 0;
T[tot][0] = T[tot][1] = 0;
}
u = T[u][v];
++ cnt[u];
}
}
inline void search (int x)
{
int u, v;
long long res;
u = 1;
res = 0;
for (int i = 1 << 30; i; i >>= 1){ // 从高位到低位, 逐位转化为K
if (!u) break ;
v = (x & i) ? 1 : 0; //每一位反过来
if (v){
if (i & k) u = T[u][0]; //继续找左子树比较
else{
res += cnt[T[u][0]]; //确定已经大于K了, 将右子树中的个数累加
u = T[u][1];
}
}
else {
if (i & k) u = T[u][1];
else{
res += cnt[T[u][1]];
u = T[u][0];
}
}
}
ans += res + cnt[u];
return;
}