洛谷3917 异或序列

原题链接

按每个数的二进制下一位一位计算贡献。
对于第\(k\)位,计算前缀异或和\(S[1\to n]\ (S[0] = 0)\),则求这一位所能产生贡献的次数即是求有多少区间满足异或值为\(1\),即求有多少\([i,j]\ (1\leqslant i\leqslant j \leqslant n)\)满足\(S[i - 1] \oplus S[j] = 1\ (\oplus \text{表示异或})\)
有结论:满足要求的区间个数等于\(S[0\to n]\)\(1\)的个数与\(0\)的个数的乘积。
而对于第\(k\)位,单个区间所产生贡献值为\(2^k\)
最后将其所有贡献累加起来即可。
时间复杂度\(O(n\times log_2MAXNUM)\)

#include<cstdio>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int a[N];
inline int re()
{
    int x = 0;
    char c = getchar();
    bool p = 0;
    for (; c < '0' || c > '9'; c = getchar())
        p |= c == '-';
    for (; c >= '0' && c <= '9'; c = getchar())
        x = x * 10 + c - '0';
    return p ? -x : x;
}
inline int maxn(int x, int y)
{
    return x > y ? x : y;
}
int main()
{
    int i, j, n, ma = 0, o, k, s_1, s_0;
    ll s = 0;
    n = re();
    for (i = 1; i <= n; i++)
    {
        a[i] = re();
        ma = maxn(ma, a[i]);
    }
    o = log2(ma);
    for (j = 0; j <= o; j++)
    {
        s_1 = k = 0;
        s_0 = 1;
        for (i = 1; i <= n; i++)
        {
            k ^= (a[i] >> j) & 1;
            k ? s_1++ : s_0++;
        }
        s += 1LL * s_1 * s_0 * (1 << j);
    }
    printf("%lld", s);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/Iowa-Battleship/p/9829500.html