zoukankan      html  css  js  c++  java
  • Dirichlet 前缀和与快速莫比乌斯变换(FMT)

    一类常见的问题是:

    给出一个 (k^n) 的数组,求出它的高维前缀和。

    这种问题的最常见做法是 (mathcal{O}(k^n2^n)) 的容斥做法,这种做法复杂度随着维度升高快速增加。

    高维前缀和的思想就是对于每一个维度分别求和,这显然是正确的。具体来说,我们先考虑将一个 (n) 维数组改写成一维,那么一种显然的想法就是,如果原来的数组位置是 ((alpha_0,alpha_1,cdots,alpha_{n-1})),那么可以压缩到 (alpha_0k^{0}+alpha_1k^{1}+cdots=sum_{i=0}^{n-1}alpha_ik^i)。那么这时候我们发现,其实可以重复利用信息来化简计算:

    for(int i = 0, cur = 1; i < n; ++i, cur = cur * k)
        for(int j = 0; j < pow(k, n); ++j)
            if((j / cur) % k < k - 1) a[j + cur] += a[j];
    

    这为什么是对的呢?可以想象这是一张有向无环图,那么我们相当于是按照拓扑序在进行 dp,这显然是正确的。

    这种方法不仅在这里适用,我们考虑一种常见的数论问题:

    给出一个数列 (a_n),输出一个数列 (b_n),满足 (b_n=sum_{d|n}a_d)

    一种常见的做法是对于每一个 (a_i),枚举 (i) 的倍数,一个一个加上去,这样的复杂度是 (sum_{i=1}^nlfloor n/i floor=mathcal{O}(nlog n)) 的。

    一种优化是,考虑唯一分解定理,每一个数都可以被表为素数空间上的一串数,或者说,所有的素数构成了一组正交基。于是我们可以使用类似的算法进行优化:

    for(int i = 1; i <= cnt; ++i)
        for(int j = 1; j * prime[i] <= n; ++j)
            a[j * prime[i]] += a[j];
    

    这就是 Dirichlet 前缀和。这样的复杂度是 (sum_{pleq n,pinmathbb{P}}lfloor n/p floor=mathcal{O}(nloglog n)) 的,和 Eratosthenes 筛法相同。

    还有一类问题则是子集的问题,这就是快速莫比乌斯变换(FMT)。这种问题形式如下:

    给出数列 (a_n,b_n),求出 (c_n) 满足 (c_n=sum_{i|j=n}a_ib_j)

    考虑 FFT 的过程,我们需要求出一个函数 (hat{a}=F(a)) 可以将一个数列映射到另一个数列,使得 (hat{a}_i=hat{b}_ihat{c}_i)

    一种构造就是

    [hat{a}_n=sum_{i|n=n}a_i ]

    也即是 (k=2) 情况下的 (a) 的高维前缀和。关于如何构造这类函数,请参考下篇博客

    那么考虑逆变换 (a=F^{-1}(hat a)),这可以看作是扣除贡献,于是:

    void FMT(int *a, int N, int op) {
        for(int i = 1; i < N; i <<= 1)
          	for(int j = 0; j < N; ++j)
                if(~j & i) a[j + i] += op * a[j];
    }
    

    于是这种问题现在就可以 (mathcal{O}(nlog n)) 地完成。

  • 相关阅读:
    Memcached简介
    TP5 volist
    Getting command line access to PHP and MySQL running MAMP on OSX
    PHP use
    PHP 命名空间(namespace)
    mac 使用 pf 做端口转发
    微信测试帐号如何设置URL和Token,以及相关验证的原理
    ionic开发笔记
    Eclipse下配置Maven
    css引用第三方字体库
  • 原文地址:https://www.cnblogs.com/whx1003/p/13978772.html
Copyright © 2011-2022 走看看