题目大意
给出nnn个数a1,a2,⋯ ,ana_1,a_2,\cdots,a_na1,a2,⋯,an,每个数在二进制下,可以任意交换0和1的位置(也可以不交换),问有多少个区间[l,r][l,r][l,r]满足在每个数在操作之后的异或和为0。
时间限制
2s
数据范围
n≤3×105n\le 3\times 10^5n≤3×105
ai≤1018a_i\le 10^{18}ai≤1018
题解
不难发现,只需要关心这个数在二进制下1的个数,而不要具体关注这个数是什么。
那么考虑一下满足条件的区间中1的个数,
显然1的个数一定是偶数,不然1无法两两匹配在异或的时候消去。
除了这个条件,还有没有别的条件,
显然是有的,来看这种情况:
两个相邻的数,一个数有31个1,另一个数只有1个1,它们的和是偶数,但是它们并不满足条件。
因此多了一个条件,这个区间其它数1的个数的和要比的最多1的那个数要大。
不难发现,这个最大值只有60,对于这部分情况可以之间枚举,其余的情况通过前缀和计算。
Code
//#pragma GCC optimize (2)
//#pragma G++ optimize (2)
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#include <queue>
#include <map>
#include <set>
#define ll long long
#define G getchar
using namespace std;
ll read()
{
char ch;
for(ch = G();(ch < '0' || ch > '9') && ch != '-';ch = G());
ll n = 0 , w;
if (ch == '-')
{
w = -1;
ch = G();
} else w = 1;
for(;'0' <= ch && ch <= '9';ch = G())n = (n<<1)+(n<<3)+ch-48;
return n * w;
}
void write(int x)
{
if (x > 9)
{
write(x / 10);
putchar(x % 10 + 48);
}
else putchar(x + 48);
}
const int N = 300005;
int n , s[N] , num[N][3] , tmp , mx , a[N];
ll ans , x , z[63];
int main()
{
//freopen("d.in","r",stdin);
//freopen("e.out","w",stdout);
z[0] = 1;
for (int i = 1 ; i < 63 ; i++)
z[i] = z[i - 1] << 1;
n = read();
num[0][0] = 1;
memset(a , 0 , sizeof(a));
for (int i = 1 ; i <= n ; i++)
{
x = read();
for (int j = 0 ; z[j] <= x ; j++)
if (z[j] & x) a[i]++;
s[i] = (s[i - 1] + a[i]) % 2;
num[i][0] = num[i - 1][0];
num[i][1] = num[i - 1][1];
num[i][s[i]]++;
mx = tmp = a[i];
for (int j = i - 1 ; j ; j--)
{
if (tmp > 120)
{
ans = ans + num[j - 1][s[i]];
break;
}
tmp = tmp + a[j];
mx = max(mx , a[j]);
if (tmp >= mx * 2 && tmp % 2 == 0) ans++;
}
}
printf("%lld\n", ans);
return 0;
}

本文探讨了如何解决一个关于给定数值集合的计数问题,即在允许任意交换二进制位后,求满足异或和为零的区间。关键在于理解每个数的1的奇偶性,并注意到区间内1的总数限制。通过前缀和和特殊情况枚举,给出了高效的解题代码和算法思路。

被折叠的 条评论
为什么被折叠?



