• 杜教筛


    曾经学过,但当时只是浅薄了理解了一下,并没有学会实际应用,所以又来学了一遍

    ——前言

    前置定义

    数论函数

    积性函数

    狄利克雷卷积

    常见积性函数

    [egin{aligned} 1(n)&=1\ epsilon(n)&=[n=1]\ sigma_k(n)&=sum_{i|n}i^k\ id_k(n)&=n^k\ varphi(n)&=sum_i^n[gcd(i,n)=1]\ mu(n)&= left{egin{matrix} 1&n=1\ (-1)^x&prod_{i=1}^x k_i=1\ 0&max{k_i}geq 2 end{matrix} ight. end{aligned}]

    常见积性函数之间的变换及证明

    • (mu*1=epsilon)

    [egin{aligned} (mu*1)(n)&=sum_{d|n}mu(d)1(frac{n}{d})\ &=sum_{d|n}mu(d)\ &=[n=1]\ &=epsilon(n) end{aligned}]


    • (varphi*1=id_1)

    [egin{aligned} (varphi*1)(n)&=sum_{d|n}varphi(d)1(frac{n}{d})\ &=sum_{d|n}varphi(d) end{aligned}]

    考虑枚举 (d) 表示 (1sim n) 中某个数与 (n)(gcd)(d),那么当 (din[1,n]) 时,得到的 (sum_i^n[gcd(i,n)=d]) 的和就是 (n)

    现在考虑如何求 (sum_i^n[gcd(i,n)=d]),把 (n) 拆分成 (n imes frac{n}{d}) 的形式,考虑一个满足条件的 (i(ileq n)),将 (i) 拆分成 (k imes d) 的形式,显然有 (kleq frac{n}{d}),又因为 (gcd(i,n)=d),所以需要满足 (kperp frac{n}{k}),那么满足条件的 (i) 只会有 (varphi(frac{n}{d})) 个,所以容易得到

    [egin{aligned} sum_{d|n}varphi(frac{n}{d})&=id_1(n)\ ecause sum_{d|n}varphi(frac{n}{d})&=sum_{d|n}varphi(d)\ &=(varphi*1)(n)\ herefore(varphi*1)(n)&=id_1(n) end{aligned} ]


    • (mu*id_1=varphi)

    (f(d)) 表示 (1sim n) 中与 (n)(gcd)(d) 的个数,(g(d)) 表示 (1sim n) 中与 (n)(gcd)(d) 的倍数的个数

    显然有

    [g(d)=[d|n]frac{n}{d} ]

    由莫比乌斯反演得

    [egin{aligned} f(g)&=sum_{g|d}mu(frac{d}{g})g(d)\ &=sum_{g|d,d|n}mu(frac{d}{g})frac{n}{d}\ f(1)&=sum_{d|n}mu(d)frac{n}{d} end{aligned} ]

    容易发现 (f(1)) 其实就是 (1sim n) 中与 (n) 互质的个数

    [egin{aligned} (mu*id_1)(n)&=sum_{d|n}mu(d)1(frac{n}{d})\ &=f(1)\ &=varphi(n) end{aligned} ]

    杜教筛

    杜教筛常用于求解一些数论函数的前缀和,由杜瑜皓杜老师研究并发明

    经常与洲阁筛、(min25) 筛相提并论,因其对选手的数学能力要求不高、较为优秀的时间复杂度和代码实现的简短,常作为三大筛法的优先学习对象

    下面这个图展示了三个筛法在不同数据范围下的算法效率,容易发现杜教筛的复杂度变化相对于其他两个来说是较为平稳的

    基本问题

    给定一个数论函数 (f(x)),求出 (f(x)) 的第 (n) 项前缀和 (S(n))(nleq 10^{10})

    算法核心

    杜教筛的核心是构造一个数论函数 (g(x)),从而得到 (h=f*g)(h) 的第 (n) 项前缀和很好求出,然后通过一些推导从而求出 (S(n))

    通项公式

    因为 (f*g) 的前缀和很好求,考虑用它去推得 (S(n))

    [sum_{i=1}^n(f*g)(i)=sum_{i=1}^nsum_{d|i}f(d)g(frac{i}{d}) ]

    然后我们将后面那个狄利克雷卷积的形式改变一下

    我们原来的表示是

    [a[i]=sum_{d|i}b[d]c[frac{i}{d}] ]

    我们现在改成

    [a[i]=sum_{i=j imes k}b[j]c[k] ]

    显然是正确的吧

    然后又因为我们只需要求卷积后每项的和,所以只需要考虑每对 (j,k) 对和的贡献就行了,所以上面推到一半的式子可以转化成

    [egin{aligned} ecause sum_{d=1}^ng(d)sum_{i=1}^{left lfloorfrac{n}{d} ight floor} f(i)&=sum_{d=1}^ng(d)S(left lfloorfrac{n}{d} ight floor)\ herefore sum_{i=1}^n(f*g)(i)&=sum_{d=1}^ng(d)S(left lfloorfrac{n}{d} ight floor)\ g(1)S(n)&=sum_{i=1}^n(f*g)(i)-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)\ S(n)&=frac{sum_{i=1}^n(f*g)(i)-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{g(1)}\ end{aligned} ]

    最后我们得到了一个 (S(n)) 的递推式,整除分块递归求解即可

    算法效率

    通常情况下,(sum_{i=1}^n(f*g)(i)) 是可以 (mathcal{O}(1)) 求出的,一段 ([l,r])(g(d)) 之和也可以 (mathcal{O}(1)) 得到,所以时间复杂度主要集中在递归求解上,而且通常情况下会对 (S(n)) 进行记忆化

    由于整除分块的复杂度是 (mathcal{O}(sqrt{n})),而且 (left lfloorfrac{n}{d} ight floor) 的取值只有 (sqrt{n}) 个,所以最后的复杂度是

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

    我们可以通过线性筛先预处理出来较小的 (n)(S(n))(n) 很小时就没必要再递归下去了,这样复杂度可以优化到 (mathcal{O}(n^{frac{2}{3}}))

    算法模板

    提前会有一个线性筛,剩下的就只剩一个递归函数了

    inline ll F (register ll n) {
    	if (n <= 3e6) return sumf[n]; // 预处理出 n 较小时的前缀和
    	if (f[n]) return f[n]; // 记忆化,如果求过这个值,就不需要再递归一遍了
    	register ll ans = sum (f * g); // 这是 f * g 的 n 项前缀和
    	for (register ll l = 2, r; l <= n; l = r + 1) // 整除分块
    		r = n / (n / l), ans -= (sumg[r] - sumg[l - 1]) * F (n / l); 
    		// [l,r] 的 F (n / l) 是一样的,对 g(x) 求个和即可
    	return f[n] = ans / g[1]; // 别忘了除上 g(1)
    }
    

    常见数论函数的前缀和推导

    • (f(n)=mu(n),S(n)=sum_{i=1}^{n}mu(i))

    构造函数 (g(n)=1(n)),容易得到 (f*g=epsilon)(详见上面证明),套用公式

    [egin{aligned} S(n)&=frac{sum_{i=1}^n(f*g)(i)-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{g(1)}\ &=frac{sum_{i=1}^nepsilon(i)-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{g(1)}\ &=frac{1-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{g(1)}\ &=frac{1-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{1}\ &=1-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor) end{aligned} ]

    因为 (g(n)=1(n)),所以 (g(n)) 的前缀和可以直接 (mathcal{O}(1)) 求,就可以直接套用模板做了


    • (f(n)=varphi(n),S(n)=sum_{i=1}^{n}varphi(i))

    构造函数 (g(n)=1(n)),容易得到 (f*g=id_1)(详见上面证明),套用公式

    [egin{aligned} S(n)&=frac{sum_{i=1}^n(f*g)(i)-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{g(1)}\ &=frac{sum_{i=1}^nid_1(i)-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{g(1)}\ &=frac{frac{n(n+1)}{2}-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{g(1)}\ &=frac{frac{n(n+1)}{2}-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{1}\ &=frac{n(n+1)}{2}-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor) end{aligned} ]

    同样可以套用模板去做


    • (f(n)=mu(n)n,S(n)=sum_{i=1}^{n}mu(i)i)

    构造函数 (g(n)=id_1(n))

    [egin{aligned} (f*g)(i)&=sum_{d|i}f(d)g(frac{i}{d})\ &=sum_{d|i}mu(d)d imes frac{i}{d}\ &=sum_{d|i}mu(d)i\ &=isum_{d|i}mu(d)\ &=i[i=1]\ &=epsilon(i) end{aligned} ]

    接着套用公式

    [egin{aligned} S(n)&=frac{sum_{i=1}^n(f*g)(i)-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{g(1)}\ &=frac{sum_{i=1}^nepsilon(i)-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{g(1)}\ &=frac{1-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{g(1)}\ &=frac{1-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{1}\ &=1-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor) end{aligned} ]

    因为 (g(n)=id_1(n)),所以可以 (mathcal{O}(1)) 计算前缀和,同样可以套用模板去做


    • (f(n)=varphi(n)n,S(n)=sum_{i=1}^{n}varphi(i)i)

    构造函数 (g(n)=id_1(n))

    [egin{aligned} (f*g)(i)&=sum_{d|i}f(d)g(frac{i}{d})\ &=sum_{d|i}varphi(d)d imes frac{i}{d}\ &=sum_{d|i}varphi(d)i\ &=isum_{d|i}varphi(d)\ &=i imes i\ &=i^2 end{aligned} ]

    接着套用公式

    [egin{aligned} S(n)&=frac{sum_{i=1}^n(f*g)(i)-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{g(1)}\ &=frac{sum_{i=1}^ni^2-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{g(1)}\ &=frac{frac{n(n+1)(2n+1)}{6}-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{g(1)}\ &=frac{frac{n(n+1)(2n+1)}{6}-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{1}\ &=frac{n(n+1)(2n+1)}{6}-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor) end{aligned} ]

    然后套用模板去做就行了


    • (f(n)=mu(n)n^2,S(n)=sum_{i=1}^{n}mu(i)i^2)

    构造函数 (g(n)=id_2(n))

    [egin{aligned} (f*g)(i)&=sum_{d|i}f(d)g(frac{i}{d})\ &=sum_{d|i}mu(d)d^2 imes frac{i^2}{d^2}\ &=sum_{d|i}mu(d)i^2\ &=i^2sum_{d|i}mu(d)\ &=i^2[i=1]\ &=epsilon(i) end{aligned} ]

    接着套用公式

    [egin{aligned} S(n)&=frac{sum_{i=1}^n(f*g)(i)-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{g(1)}\ &=frac{sum_{i=1}^nepsilon(i)-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{g(1)}\ &=frac{1-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{g(1)}\ &=frac{1-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{1}\ &=1-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor) end{aligned} ]

    因为 (g(n)=id_2(n)),所以前缀和直接就是 (frac{n(n+1)(2n+1)}{6}),同样可以套用模板去做


    • (f(n)=varphi(n)n^2,S(n)=sum_{i=1}^{n}varphi(i)i^2)

    构造函数 (g(n)=id_2(n))

    [egin{aligned} (f*g)(i)&=sum_{d|i}f(d)g(frac{i}{d})\ &=sum_{d|i}varphi(d)d^2 imes frac{i^2}{d^2}\ &=sum_{d|i}varphi(d)i^2\ &=i^2sum_{d|i}varphi(d)\ &=i^2 imes i\ &=i^3 end{aligned} ]

    接着套用公式

    [egin{aligned} S(n)&=frac{sum_{i=1}^n(f*g)(i)-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{g(1)}\ &=frac{sum_{i=1}^ni^3-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{g(1)}\ &=frac{frac{n^2(n+1)^2}{4}-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{g(1)}\ &=frac{frac{n^2(n+1)^2}{4}-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor)}{1}\ &=frac{n^2(n+1)^2}{4}-sum_{d=2}^ng(d)S(left lfloorfrac{n}{d} ight floor) end{aligned} ]

    (sum g(d))的求法跟上面一样,接着还是套用模板去做就行了

    例题

    模板题,其实就是前两个推导,就不写题解了

    选数题解

    神犇和蒟蒻题解

    如果你懂了杜教筛核心思想,做题基本上就差不多了

    本质上是这个递推函数的变形,剩下的问题主要就是 (g(n)) 的构造和 (f*g) 的推导,数学足够强的话应该也不构成问题

    因为博主太菜了,或许不会再更新其他两个筛法的博客了

    例题可能会看心情实时更新,但博主是个老鸽子了,毕竟连游记都懒得写

    ——后记

  • 相关阅读:
    PortalBasic Java Web 应用开发框架:应用篇(一) —— 配置文件
    PortalBasic Java Web 应用开发框架:应用篇(六) —— 公共组件
    PortalBasic Java Web 应用开发框架:应用篇(三) —— 国际化
    普通软件项目开发过程规范(五)—— 总结
    PortalBasic Java Web 应用开发框架:应用篇(四) —— 文件上传和下载
    PortalBasic Java Web 应用开发框架 —— 前言
    PortalBasic Java Web 应用开发框架:应用篇(二) —— Action 使用
    dll 问题 (转)
    不同服务器数据库之间的数据操作
    USB鼠标经常出现不能用的情况——解决方法
  • 原文地址:https://www.cnblogs.com/Rubyonly233/p/14925239.html
Copyright © 2020-2023  润新知