题意

小AA找到了一个数列,她想要知道这个数列中所有长度为偶数的区间异或和之和 。
后来她发现这个问题太简单了,于是她加了一个限制,要求区间长度在[L,R]之间,
然后她就不会了。。。
请你告诉她问题的答案。

输入描述

第一行三个数 n, L, R
第二行n个数表示这个数列。

输出描述

输出一行表示答案,由于答案可能很大,请输出答案模的值。

解析

我们先来一点一点的解读这个题目,首先是在数列中求区间的异或和,偶数我们先不管。

看到这个异或和,我们就要考虑到这个应该是要位运算算贡献,异或和,1xor1=0,0xor0=0,1xor0=1;可见在两个之间异或的话必须是有一个1一个0才能得到1,在区间中呢,0不算贡献,只有1为奇数个时,才能得到1,我们要求每一个区间的异或和,我们就要算出每一个区间的贡献。

然后就是这些区间的和,看到这个我们不难想到使用前缀和,前缀和可以很快的求出每一个区间的值。

我们结合两个,用前缀和来记录所有的贡献,定义一个数组

int sum[MAXN][30];

后面的这个30表示前面这一位上所有1的个数,这样就可以很快的得到每一位的贡献。

这里的sum只是统计了1的个数,我们还要得到前面所有数的异或和的前缀和,这里我们再定义一个数组

int cnt[MAX][30][2];

用来记录异或和之和,最后一维0/1表示前j位sum为奇/偶的个数

接下来我们考虑区间长度为偶数,以及区间长度在[L,R]之间。以为长度为偶数,我们要每隔一个数进行计算,
右端点枚举到才L开始存左端点,当右端点大于等于R时开始维护区间长度,即减去最先存进来的左端点,保证区间长度不大于R

还要注意如果越过最大长度也就是区间比R大了,因为当前已经计算过了,下次计算应该把第一次进的左端点次数减掉。

代码

#include <iostream>
using namespace std;
typedef long long ll;

const int MOD = 1e9 + 7;
const int MAX = 1e5 + 7;
int n, L, R;
int sum[MAX][30];
int cnt[MAX][30][2];

int main() {
    cin >> n >> L >> R;
    for (int i = 1; i <= n; i++) {
        int info; cin >> info;
        for (int j = 29; ~j; --j) {
            sum[i][j] = sum[i - 1][j] + ((info >> j) & 1);
            if (i != 1) {
                cnt[i][j][0] = cnt[i - 2][j][0];
                cnt[i][j][1] = cnt[i - 2][j][1];
            }
            cnt[i][j][sum[i][j] & 1]++;
        }
    }
    //初始化
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        int left = i + L - 1, right = min(n, i + R - 1);//为了防止越界
        if ((left - i + 1) & 1) left++;
        if ((right - i + 1) & 1) right--;
        if (left > right) continue;//特判缩小范围后越界的情况

        for (int j = 29; ~j; j--) {
            int v = (sum[i - 1][j] & 1) ^ 1;
            ll tot = cnt[right][j][v] - cnt[left - 2][j][v];
            ans = (ans + (1 << j) * tot % MOD) % MOD;
        }
    }
    cout << ans << endl;
    return 0;
}