• 杜教筛学习笔记


    杜教筛是一种在亚线性复杂度下快速得到积性函数前缀和的一种算法。

    前置芝士:狄利克雷卷积、莫比乌斯反演(戳这里

    先从一个简单的问题入手

    (sumlimits_{i=1}^{n}f(i)),这里的(f(n))为一个积性函数,(n leq 10^9)

    显然(n)的范围要求我们必须找到一个低于线性时间复杂度的算法。

    定义(S(n) = sumlimits_{i=1}^{n}f(i)),再定义另一个积性函数(g),这个积性函数(g)的选择十分讲究,具体是什么需要结合具体问题来确定。

    (f)(g)的狄利克雷卷积的前缀和:

    [sumlimits_{i=1}^{n} (f*g)(i) ]

    [= sumlimits_{i=1}^{n} sumlimits_{d|i} f(d)g(frac{i}{d}) (狄利克雷卷积定义) ]

    [= sumlimits_{d=1}^{n} g(d) sumlimits_{i}^{lfloor frac{n}{d} floor} f(i) (交换求和顺序,相当于因式分解) ]

    [= sumlimits_{d=1}^{n} g(d) S(lfloor frac{n}{d} floor) (后边变成前缀和形式) ]

    我们要求的是(S(n)),不妨先考虑(g(1)S(n)):

    [g(1)S(n) = sumlimits_{d=1}^{n} g(d) S(lfloor frac{n}{d} floor) - sumlimits_{d=2}^{n} g(d) S(lfloor frac{n}{d} floor) ]

    这个式子十分显然。

    而根据上边前缀和的推导,又可以知道:

    [g(1)S(n) = sumlimits_{i=1}^{n} (f*g)(i) - sumlimits_{d=2}^{n} g(d) S(lfloor frac{n}{d} floor) ]

    好的那么我们想求(S(n)),我们就需要求出两坨式子之差,左边那一坨是我们构造的函数,我们可以通过玄学的设计使得让(f*g)的前缀和尽可能好求。而右边那一坨,我们注意到其实跟(S(lfloor frac{n}{d} floor))有关,那么就可以进行递归求解,结合上数论分块就搞定了。

    现在我们就得到了杜教筛的套路式,根据这个式子套入函数,设计一个(g)函数就可以做了。

    关于复杂度分析,假如全程递归求解的话,设求出(S(n))的复杂度是(T(n)),而根据套路式可知,想求出(S(n)),就需要知道(sqrt{n})(S(lfloor frac{n}{i} floor)),因此有等式:

    [T(n) = sumlimits_{i=1}^{sqrt n} O(sqrt i) + O(sqrt {frac{n}{i}})=O(n^{frac{3}{4}}) ]

    最后的得数是根据主定理得到的,不会的请参考算法导论(我也不会)

    进一步优化,如果我们通过线性筛筛出前(m)(S),那么复杂度可以进一步降低:

    [T(n) = sumlimits_{i=1}^{lfloor frac{n}{m} floor} sqrt frac{n}{i} = O({frac{n}{sqrt m}}) ]

    (m=n^{frac{2}{3}})时,复杂度为(O(n^{frac{2}{3}}))

    对于多组询问,如果用哈希表/unordered_map记录一下算过的答案可以获得常数优化。

    下面是例题:

    Luogu P4213模板题
    求两个喜闻乐见的函数((mu)(varphi))的前缀和。

    先考虑(mu),首先代入套路式:

    [g(1)S(n) = sumlimits_{i=1}^{n} (mu*g)(i) - sumlimits_{d=2}^{n} g(d) S(lfloor frac{n}{d} floor) ]

    然后开始构造函数(g),因为我们知道,函数(u(n)=1)(mu)在狄利克雷卷积中互为逆元,因此这里的(g)函数直接选择(u),这样左边那一坨直接就是1了。

    [S(n) = 1 - sumlimits_{d=2}^{n} u(d) S(lfloor frac{n}{d} floor) ]

    这样就可以数论分块求解了。

    ll get_mu(ll n)
    {
    	if(n <= N - 10) return mu[n];
    	if(ansmu[n]) return ansmu[n];
    	ll ret = 1ll;
    	for(ll l = 2,r = 0;r < 2147483647 && l <= n;l = r + 1ll)
    	{
    		r = n / (n / l);
    		ret -= 1ll * (r - l + 1) * get_mu(n / l);
    	}
    	return ansmu[n] = ret;
    }
    

    (varphi)稍稍难一丢丢。根据一个结论

    [sumlimits_{d|n} varphi(d) = n ]

    转化成狄利克雷卷积形式,有

    [id = varphi * u ]

    其中(id(n)=n)

    这样代入套路式:

    [S(n) = frac{(n+1) imes n}{2} - sumlimits_{d=2}^{n} u(d) S(lfloor frac{n}{d} floor) ]

    ll get_phi(ll n)
    {
    	if(n <= N - 100) return phi[n];
    	if(ansphi[n]) return ansphi[n];
    	ll ret = 1ll * n * (n + 1) / 2;
    	for(ll l = 2,r = 0;r < 2147483647 && l <= n;l = r + 1ll)
    	{
    		r = n / (n / l);
    		ret -= 1ll * (r - l + 1) * get_phi(n / l);
    	}
    	return ansphi[n] = ret;
    }
    

    这样就可以AC模板题了!

  • 相关阅读:
    透明数据加密 (TDE)常见问题解答
    oracle wallet使用与维护
    Mybatis 一对一、一对多、多对一
    Mybatis-Plus
    eclipse安装spring boot插件spring tool suite
    springboot在idea实现热部署
    springboot在eclipse实现热部署
    SpringBoot配置文件-application.properties详解
    Dubbo入门
    Shell入门
  • 原文地址:https://www.cnblogs.com/lijilai-oi/p/12426202.html
Copyright © 2020-2023  润新知