CodeForces 1030E Vasya and Good Sequences 位运算 思维

原题链接

题意


  • 目前我们有一个长为n的序列,我们可以对其中的每一个数进行任意的二进制重排(改变其二进制表示结果的排列),问我们进行若干次操作后得到的序列,最多能有多少对 \(l, r\) 使得 \([l, r]\)区间内的异或和为0。

思路


  • 首先注意到“二进制重排”,实际上也就是说,\(a_i\)是多少不重要,有用的信息是它本身有多少个1。

  • 然后,\([L, r]\)内异或和为0,可以认为是这个区间内的1能够相互抵消。那么这个区间内1的个数一定是偶数个。

  • 我们考虑1的个数为偶数个的情况下,如何才会出现不能相互抵消的情况。这种“特殊情况”其实就是存在一个数,它的1的数量比其他的数加起来都多。除了这种情况外,其他情况下都可以全部抵消。

  • 那么我们可以预处理出 \(pre[i]\),代表\(1 - i\)中有多少个1,然后从头开始扫描,同时记录两个变量,一是\(1 - i - 1\)中有多少个\(pre[j]\)为奇数,二是多少个为偶数。然后我们在\(i\)处时,给答案加上代表与\(pre[i]\)相同奇偶性的变量即可。

  • 在扫描的同时,我们需要减去答案中的所有符合“特殊情况”的\([l, i]\)区间数量。但是我们并不需要从1开始枚举,因为每个二进制数最多不超过63个1,所以事实上我们的枚举范围为\([i - 63, i]\)即可,剩下的长度大于63的区间一定不会符合“特殊情况”。

AC代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <map>

using namespace std;

const int N = 300000;

long long abss(long long a)
{
	if (a < 0)
	{
		a = -a;
	}
	return a;
}

long long n;
long long xx;
long long aa[N + 5] = {0}, su[N + 5];
long long mm[105] = {0};

int main()
{
	mm[0] = 1;
	scanf("%lld", &n);
	long long ans = 0;
	su[0] = 0;
	for (int i = 1; i <= n; ++i)
	{
		scanf("%lld", &xx);
		while (xx)
		{
			if (xx & 1)
			{
				++aa[i];
			}
			xx >>= 1; 
		}
		su[i] = su[i - 1] + aa[i];
	}
	long long l[2] = {1, 0};
	aa[0] = 0;
	for (int i = 1; i <= n; ++i)
	{
		int bitt = su[i] & 1;
		ans += l[bitt];
		long long mx = aa[i];
		long long rem = 0;
		for (int j = i - 1; j >= 0 && j >= i - 63; --j)
		{
			if ((su[i] - su[j]) % 2 == 0 && rem < mx)
			{
				--ans;
			}
			if (mx < aa[j])
			{
				rem += mx;
				mx = aa[j];
			}
			else
			{
				rem += aa[j];;
			}
		}
		++l[bitt];;
	}
	printf("%lld", ans);;
	return 0;
}

posted @ 2021-02-22 19:53  _int_me  阅读(48)  评论(0编辑  收藏  举报