Codeforces665E Beautiful Subarrays (01Trie)

题目链接: 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} srsl1​​​.

我们考虑按二进制位从高到低枚举 k k k, 有两种情况:
k k k这位为0, 则如果 s r ⊕ s l − 1 s_r ⊕ s_{l - 1} srsl1的这位结果为1, 则一定满足要求, 计入结果即可.
k k k​这位为1, 则我们需要继续向后枚举.

最后再加上异或结果等于 k k k的贡献.


01Trie

通过上面分析, 当我们固定了 s r s_r sr​后, 我们需要统计有多少个 s l − 1 s_{l - 1} sl1​​满足要求. 因此我们不妨通过01Trie来存储前缀和信息.

我们在树中维护每个节点的数字个数即可.


代码细节: 由于我们是要找对应的 s l − 1 s_{l - 1} sl1​, 我们选择空的前缀也是一种合法情况(相当于选择区间[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;
}

END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逍遥Fau

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值