cf1030 E. Vasya and Good Sequences(思维,位运算)
题意:
任何时候都可以随意改变任何二进制数中0和1的顺序。如果一个子段中的数被改变之后,子段的异或和为0,则称为好子段。求给定数组中的好子段数量。
\(n\le 3e5, 1\le a_i\le 1e18\)
思路:
我们只关心每个数的二进制中1的数量 \(a_i\)
一个子段是好子段,有两个条件:
- 子段中1的数量最大的数小于等于其他数中的1的总数,即子段中1的总数大于等于最大的 \(a_i\) 的两倍。否则,最大的 \(a_i\) 没法被完全抵消。
- 子段中1的总数是偶数。
若两个条件都成立,有很多方法可以把1全消掉,比如每次在最小的两个 \(a_i\) 中各取一个1配对抵消。
稍微靠谱点的证明:若有不止一个数的1的数量最大,它们先相互抵消剩一个最大数;然后从最大的数和次大的数中各取一个1配对抵消。重复以上过程,每一步都不会破坏两个条件,且每一步都能减少子段中1的总数,最后一定能把1全消掉。
从右往左做。对每个固定的区间左端点,计算满足条件的右端点的数量。对条件二,记录一下后缀子段1的总数为奇/偶的位置数。注意到如果子段的长度大于60几,条件一肯定满足;对长度比较小的子段暴力验证条件一即可。
可以先忽略条件一,再减去不符合条件一的。
const int N = 3e5 + 5;
int n, a[N]; ll ans;
int main()
{
iofast;
cin >> n; for(int i = 1; i <= n; i++)
{
ll x; cin >> x;
a[i] = __builtin_popcountll(x);
}
//后缀和,后缀和为偶/奇的r的数量
int suf, cnt[2] = {1,0}; //注意第n+1位是0,也是个偶数
for(int l = n; l; l--) //倒序统计
{
suf += a[l];
ans += cnt[suf & 1];
int mx = 0, tot = 0;
for(int r = l; r <= min(n,l+62); r++)
{ //去掉不合格的
mx = max(mx, a[r]), tot += a[r];
ans -= tot%2==0 && tot - mx < mx;
}
cnt[suf & 1] ++;
}
cout << ans;
}

浙公网安备 33010602011771号