Loading

高维前缀和

高维前缀和、快速莫比乌斯变换

高维前缀和

问题

给定数组 \(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];
posted @ 2025-02-18 21:39  Terminator-Line  阅读(165)  评论(0)    收藏  举报