cf1030 E. Vasya and Good Sequences(思维,位运算)

题意:

任何时候都可以随意改变任何二进制数中0和1的顺序。如果一个子段中的数被改变之后,子段的异或和为0,则称为好子段。求给定数组中的好子段数量。

\(n\le 3e5, 1\le a_i\le 1e18\)

思路:

我们只关心每个数的二进制中1的数量 \(a_i\)

一个子段是好子段,有两个条件:

  1. 子段中1的数量最大的数小于等于其他数中的1的总数,即子段中1的总数大于等于最大的 \(a_i\) 的两倍。否则,最大的 \(a_i\) 没法被完全抵消。
  2. 子段中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;
}

posted @ 2022-03-12 14:32  Bellala  阅读(51)  评论(0)    收藏  举报