高维前缀和
高维前缀和、快速莫比乌斯变换
高维前缀和
问题
给定数组 \(f_i\),定义 \(g_i=\sum_{j\subseteq i}f_j\),求 \(g_i\)。
\(j\subseteq i\) 即 \(j\operatorname{or} i\)。
解法
常规做法是使用 \(O(3^{n})\) 的子集枚举的 trick。
用高维前缀和可以做到 \(O(n2^{n})\) 的优秀复杂度。
具体来说,按一定顺序枚举全集 \(U\) 中的每一个元素,逐个加入这些元素。
假设现在枚举到了 \(i\),若 \(s\) 中含 \(i\),则令 \(g_s\) 加上 \(g_{s-\{i\}}\)。
因为 \(g_{s-\{i\}}\) 包含了不含 \(i\) 的,且是 \(s\) 子集的集合;而 \(g_s\) 则包含了含 \(i\),且是 \(s\) 子集的集合。
参考代码
copy 了别人写的代码
void SUM(int *a,int n){for(int i=0;i<n;i++)for(int j=0;j<(1<<n);j++)if(j&(1<<i))a[j]+=a[j^(1<<i)];}
高维差分
只需将高维前缀和中的 + 变成 - 即可。想象前缀和与差分,不正是把 + 变 - 了吗?
高维后缀和
将 \(s\) 中含有 \(i\) 改成“不含”即可。
快速莫比乌斯变换
问题
给定 \(A\) 和 \(B\),定义 \(C_i=\sum_{j\operatorname{or}k=i}A_j\times B_k\)。
解法
对 \(A\) 和 \(B\) 做高维前缀和,然后按位相乘,得到的结果恰是 \(C\) 高维前缀和的结果。再做一次高维差分就能得到真实的 \(C\) 数组了。
例题
CF165E
板题,只是因为这里应当有一份我的代码。
// g++ e.cpp -o e -std=c++14 -Wall -Wextra -Wshadow -O2
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5, V = 1 << 22;
int f[V], a[N];
int main()
{
cin.tie(0)->sync_with_stdio(0);
int n;
cin >> n;
for (int i = 1; i <= n; ++i)
{
cin >> a[i];
f[a[i]] = a[i];
}
for (int i = 0; i < 22; ++i)
{
for (int j = 0; j < V; ++j)
{
if (j >> i & 1)
{
if (!f[j])
f[j] = f[j ^ (1 << i)];
}
}
}
for (int i = 1; i <= n; ++i)
{
if (!f[(V - 1) ^ a[i]])
{
cout << "-1 ";
}
else
{
cout << f[(V - 1) ^ a[i]] << " ";
}
}
}
Dirichlet 前缀和
以每一个质数作为一维,则 Dirichlet 前缀和本质上就是高维前缀和。
但质数实际上非常多,每一个开一维不可接受,但有用状态数为 \(10^{7}\) 级别,可以不用压二进制,每个数的唯一分解形式即可以表示每一维。
for (int i = 1; i <= pril; ++i)
for (int j = 1; j * pri[i] <= n; ++j)
s[j * pri[i]] += s[j];

浙公网安备 33010602011771号