拆位问题小总结

拆位问题小总结

通用表达

异或前缀和:

\[\begin{align*} s _ k = \bigoplus_{i= 1} ^ ka_i \\ \end{align*} \]

\[\begin{align*} f(l,r) = \bigoplus_{i = l}^ra_i = s_r \oplus s_{l-1} \\ \end{align*} \]

定义:\(s_{i_j}表示s_i的二进制表示法第j数位是什么数(0或1)\)

\(cnt[i][j]:前i个s中,第i位为j的情况出现了多少次。\)

举例:\(s_{3_1} = 2\)表示\(s_1,s_2,s_3\)中有两个数的\(2^1\)数位那里为1。那么自然有1个那里为0.

[蓝桥杯 2023 省 A] 异或和之和

解题思路:

\[\begin{align*} ans &= \sum_{l = 1}^n\sum_{r = l}^nf(l,r) \\ &= \sum_{i = 0}^n\sum_{j = 0}^{31} 2^i*cnt[i][s_{i_j}\oplus1] \end{align*} \]

代码:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e6 + 10;
const int M = 2 * M;
typedef pair<int, int> pii;
#define fi first
#define se second
int n, m, k;

void solve()
{
    scanf("%d", &n);
    vector<ll> a(n + 1);
    vector<vector<ll>> f(40, vector<ll>(4));
    ll ans = 0;
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        a[i] ^= a[i - 1];
    }
    for (int i = 0; i <= n; i++)
    {
        for (int j = 0; j <= 31; j++)
        {
            ll t = a[i] >> j & 1;
            f[j][t]++;
            ans += (1ll << j) * f[j][t ^ 1];
        }
    }
    cout << ans << endl;
}

int main()
{
    int t = 1;
    while (t--)
    {
        solve();
    }
    return 0;
}

D .Sum of XOR Functions

解题思路:

\[f[k][j]表示当前二进制第k数位为j的s的下标之和。 \]

举例:

\(s_1 = 1,s_2 = 2,s_3 = 3\)

当我们搜索完\(s_1\)后,\(f[1][1] = 0 + 1 = 1\)

当我们搜索完\(s_2\)后,\(f[1][1] = 1,f[1][2] = 0 + 2 = 2\)

当我们搜索完\(s_3\)后,\(f[1][1] = 1 + 3 = 4,f[1][2] = 2+3 = 5\)

对于每一个\(s_i\)来说,假设第\(j\)位为\(1\),他的左侧有\(k\)个数的第\(j\)位为\(0\)

但对于这个数的这一位的和左边比对的的贡献为

\[\begin{align*} res &=2^i [(r-l_1) + (r - l_2) + ... + (r - l _k)]\\ &=2^i[kr - \sum_\limits{i = 1}^kl_i] \end{align*} \]

所以,我们在遍历的时候,将所有的\(l_i\)累加,并计算\(k\)即可。

\[\begin{align*} ans &= \sum_{l= 1}^n\sum_{r=l}^n f(l,r)\cdot(r - l + 1) \\ &= \sum_{l= 1}^n\sum_{r=l}^n f(l,r)\cdot r- f(l,r)\cdot (l - 1)\\ &= \sum_{l= 0}^n\sum_{r=l}^n (\sum_{i = 1} ^ {32} 2^{i} * ((s_r \& 2^i) \oplus (s_l \&2^i))*(r - l))\\ &= \sum_{i = 0}^n\sum_{j = 0}^{31} 2^i*(cnt[i][s_{i_j}\oplus1] * i - f[i][s_{i_j}\oplus1]) \end{align*} \]

代码:

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e6 + 10;
const int M = 2 * M;
const int mod = 998244353;
typedef pair<int, int> pii;
#define fi first
#define se second
int n, m, k;

void solve()
{
    scanf("%d", &n);
    vector<ll> a(n + 1);
    for (int i = 1; i <= n; i++)
    {
        scanf("%lld", &a[i]);
        a[i] ^= a[i - 1];
    }
    vector<vector<ll>> f(40, vector<ll>(4));
    vector<vector<ll>> cnt(40, vector<ll>(4));
    ll ans = 0;
    for (int i = 0; i <= n; i++)
    {
        for (int j = 32; j >= 0; j--)
        {
            ll t = a[i] >> j & 1;
            ans = (ans + (((((cnt[j][t ^ 1] * (ll)i - f[j][t ^ 1]) % mod + mod) % mod) * (1ll << j)) % mod)) % mod;
            f[j][t] = (f[j][t] + i) % mod;
            cnt[j][t] = (cnt[j][t] + 1) % mod;
        }
    }
    printf("%lld\n", ans);
}

int main()
{
    int t = 1;
    // scanf("%d", &t);
    while (t--)
    {
        solve();
    }
    return 0;
}

Problem H. xor

解题思路:

\[\begin{align*} ans &= \sum_{i = 1}^n\sum_{j = 1}^n(a_i\oplus a_j)^2\\ &= \sum_{i = 1}^n\sum_{j = 1}^n(a_i\oplus a_j)(a_i\oplus a_j)\\ \end{align*} \]

如果\(a_i \oplus a_j = 2^{\alpha_1} + 2^{\alpha_2} + ... + 2^{\alpha_k}\)

那么

\[\begin{align*} (a_i\oplus a_j)(a_i\oplus a_j) &= (2^{\alpha_1} + 2^{\alpha_2} + ... + 2^{\alpha_k})(2^{\alpha_1} + 2^{\alpha_2} + ... + 2^{\alpha_k})\\ &= \sum_{x = 0}^{30}(2^x *(a_{i_x} \oplus a_{j_x}))\sum_{y = 0} ^ {30}(2^y *(a_{i_y} \oplus a_{j_y})) \end{align*} \]

\[\begin{align*} ans &= \sum_{i = 1}^n\sum_{j = 1}^n(a_i\oplus a_j)^2\\ &= \sum_{i = 1}^n\sum_{j = 1}^n(a_i\oplus a_j)(a_i\oplus a_j)\\ &= \sum_{i = 1}^n\sum_{j = 1}^n\sum_{x = 0}^{30}(2^x *(a_{i_x} \oplus a_{j_x}))\sum_{y = 0} ^ {30}(2^y *(a_{i_y} \oplus a_{j_y})) \end{align*} \]

外层遍历两个二进制数位\((x,y)\)。对于当前\((x,y)\),即\(2^x 和2^y\).我们正常从1到n遍历,记录过程中数位\(x,y\)\(0和1\)分别出现了多少次。

然后对于当前的\(a_i\)统计对于\((x,y)\)的贡献。

其中,有四种情况\((0,0),(0,1),(1,0),(1,1)\)

\((0,0)和(1,1)才会产生贡献.(0,1和(1,0)才会产生贡献.\)

只有两位都互不相同,才会有\(2^{x + y}\)的贡献。

每遍历完一次n个元素,我们就可以更新一次\(2^{x + y}\)

由于本题\(a_i * a_j\)确实有两次,所以记得乘2

\(ans = (ans + ((cnt[1] * cnt[2] * 2) + (cnt[0] * cnt[3] * 2)) *(2^{x + y}) )\)

代码:

	#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e6 + 10;
const int M = 2 * M;
const int mod = 1e9 + 7;
typedef pair<int, int> pii;
#define fi first
#define se second
int n, m, k;

void solve()
{
    scanf("%d", &n);
    vector<ll> a(n + 1);
    for (int i = 1; i <= n; i++)
        scanf("%lld", &a[i]);
    ll ans = 0;
    for (int x = 0; x <= 30; x++)
    {
        for (int y = 0; y <= 30; y++)
        {
            vector<ll> cnt(5, 0);
            ll w = (1ll << (x + y)) % mod;
            for (int i = 1; i <= n; i++)
            {
                ll res = 0;
                ll t1 = a[i] >> x & 1;
                ll t2 = a[i] >> y & 1;
                if (t1)
                {
                    res += 1;
                }
                if (t2)
                {
                    res += 2;
                }
                cnt[res]++;
            }
            // 这里要a[i] * a[j],a[j] * a[i],*2
            ans = (ans + (((((cnt[1] * cnt[2]) % mod * 2) % mod + ((cnt[0] * cnt[3]) % mod) * 2) % mod) * w) % mod) % mod;
        }
    }
    printf("%lld", ans);
}

int main()
{
    int t = 1;
    while (t--)
    {
        solve();
    }
    return 0;
}
posted @ 2023-09-25 20:43  value0  阅读(99)  评论(0)    收藏  举报