备战Noip2018模拟赛15(A组)T1 Water 淘淘的大水题

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;
}

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值