zoukankan      html  css  js  c++  java
  • 杜教筛学习笔记

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

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

    先从一个简单的问题入手

    (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模板题了!

  • 相关阅读:
    Open source physics engine
    Free Platformers: Open Source Gamers Guide to Free Games
    安装路由后,显示已连接,却上不了网?
    http://blog.csdn.net/duanbeibei/article/details/5890436
    javascript权威指南 第8章 笔记2 Kevin
    javascript权威指南 第9章 笔记 Kevin
    javascript权威指南 笔记2 Kevin
    .Net 登录窗口 Kevin
    C# 中读XML时haschrildnodes方法老为true Kevin
    javascript权威指南 第8章 笔记 Kevin
  • 原文地址:https://www.cnblogs.com/lijilai-oi/p/12426202.html
Copyright © 2011-2022 走看看