拆位问题小总结
拆位问题小总结
通用表达
异或前缀和:
定义:\(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] 异或和之和
解题思路:
代码:
#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
解题思路:
举例:
\(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\)。
但对于这个数的这一位的和左边比对的的贡献为
所以,我们在遍历的时候,将所有的\(l_i\)累加,并计算\(k\)即可。
代码:
#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
解题思路:
如果\(a_i \oplus a_j = 2^{\alpha_1} + 2^{\alpha_2} + ... + 2^{\alpha_k}\)
那么
外层遍历两个二进制数位\((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;
}