【每日一题】36. 小AA的数列 (二进制DP)

补题链接:Here

算法涉及:位运算,DP

这道题想了很久但实在没想什么巧妙的解法,暴力的代码就不放,这里引用Kur1su 的思路

异或问题优先考虑二进制位,对于这个问题,我们需要考虑偶数长度的区间,那么先对 ([L, R]) 做处理,因为如果 (L,R) 是奇数其实加一/减一没有区别。然后处理一下前缀异或和, 因为我们有性质 (sum[l, r] = sum[r] oplus sum[l - 1])。最后我们要找到哪些是有贡献的,我们考虑枚举右端点,如果当前点的异或是 1,那么需要在前面找异或为 0 的点才有贡献,反之如果当前点的异或是 0,那么要在前面找异或为 1 的点才有贡献,此外,奇数下标要找奇数下标,偶数下标要找偶数下标,才能构成偶数长度区间。
 所以我们可以用 (dp[i][j]) 维护前面符合条件的状态数,第一维表示当前位为 (0/1,) 第二维表示当前下标为 奇/偶的状态数,直接计算贡献即可。

using ll = long long;
const int N = 1e6 + 10, mod = 1e9 + 7;

int a[N];

void solve() {
    int n, l, r; cin >> n >> l >> r;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
        a[i] ^= a[i - 1];
    }
    if (l & 1) l++; // 保证区间为偶数长度
    if (r & 1) r--; // 保证区间为偶数长度
    if (l > r) { cout << 0; return ;}
    ll ans = 0;
    for (int i = 0; i < 32; ++i) { // 二进制运算
        ll p = (1ll << i);
        ll dp[2][2] = {0};
        ll num = 0;
        for (int j = l; j <= n; ++j) {
            dp[(a[j - l] >> i) & 1][(j - l) & 1]++;
            num += dp[((a[j] >> i) & 1) ^ 1][j & 1];
            num %= mod;
            if (j >= r)dp[(a[j - r] >> i) & 1][(j - r) & 1]--;
        }
        ans += num * p % mod;
        ans %= mod;
    }
    cout << ans;
}

The desire of his soul is the prophecy of his fate
你灵魂的欲望,是你命运的先知。

原文地址:https://www.cnblogs.com/RioTian/p/14814994.html