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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了每日一题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;
}

以上是关于每日一题36. 小AA的数列 (二进制DP)的主要内容,如果未能解决你的问题,请参考以下文章

寒假每日一题(入门组)week5 完结

[每日一题2020.06.14]leetcode #70 爬楼梯 斐波那契数列 记忆化搜索 递推通项公式

2021春季每日一题week3 未完结

每日一题873. 最长的斐波那契子序列的长度

笔试强训每日一题

每日一题31.「土」秘法地震 (二维前缀和 / DP)