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

    前言

    这里假设你已经会了以下东西:

    • 狄利克雷卷积
    • 莫比乌斯反演
    • 数论分块

    正文

    一切的开始

    先来看一个题目(Bzoj 4805):

    给你一个整数$n$,求以下东西:

    求$sum_{i=1}^n varphi_i$

    可以线性筛吧...但是如果$nleq 2e9$呢?

    杜教筛是啥?

    设$mathbf S(n)=sum_{i=1}^nmathbf f(i)$,其中$mathbf f$是积性函数。

    我们不妨设一个$mathbf g$,不管它具体是啥,做一个狄利克雷卷积:

    $$ (mathbf gast mathbf f)(i)=sum_{d|i}mathbf g(d)mathbf f(frac id) $$

    然后再求一个和:

    $$ sum_{i=1}^nsum_{d|i}mathbf g(d)mathbf f(frac id) \=sum_{d=1}^nmathbf g(d)sum_{d|i}mathbf f(frac id) \=sum_{d=1}^nmathbf g(d)sum_{i=1}^{n/d}mathbf f(i) \=sum_{d=1}^nmathbf g(d)mathbf S(frac nd) $$

    然后还有:

    $$ mathbf g(1)mathbf S(n)=sum_{i=1}^nmathbf g(i)mathbf S(frac ni)- sum_{i=2}^nmathbf g(i)mathbf S(frac ni) $$

    前面减数就是我们之前推的那个东西,后面被减数可以数论分块递归算:

    $$ mathbf g(1)mathbf S(n)=sum_{i=1}^n(mathbf gast mathbf f)(i)- sum_{i=2}^nmathbf g(i)mathbf S(frac ni) $$

    比如要求$sum_{i=1}^nmu(i)$

    $$ mathbf g(1)mathbf S(n)=sum_{i=1}^n(mathbf gast mu)(i)- sum_{i=2}^nmathbf g(i)mathbf S(frac ni) $$

    找一个积性函数让他们的狄利克雷卷积很好算:

    我们知道$mu$的逆为$1$,也就是说$(1astμ)=epsilon$

    那么单位元的前缀和是1,舒服啊,所以取$mathbf g(n)$为$mathbf 1(n)=1$,

    所以原式可化为:

    $$ mathbf S(n)=1-sum_{i=2}^nmathbf S(frac ni) $$

    那么回归到例题

    $$ mathbf S(n)=sum_{i=1}^n varphi_i \mathbf g(1)mathbf S(n)=sum_{i=1}^n(mathbf gast varphi)(i)- sum_{i=2}^nmathbf g(i)mathbf S(frac ni) $$

    这时我们要找一个$mathbf g$使得$mathbf g$和$mathbf g ast varphi$的前缀和都很好算

    这时我们想到了$varphi=mu*mathbf{id}$,所以啊:$varphi*mathbf 1=mathbf{id}$,所以同样取$mathbf g(n)$为$mathbf 1(n)=1$

    $$ \mathbf S(n)=frac{n imes (n+1)}{2}- sum_{i=2}^nmathbf S(frac ni) $$

    所以,杜教筛的重点就在于选取一个有利于计算的$mathbf g$...

    下面给出这题的代码:

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using std::min; using std::max;
    using std::swap; using std::sort;
    typedef long long ll;
    
    const int N = 2147483647, M = 2e6 + 10, K = 1.3e3 + 10;
    ll phi[M], s1[M], s2[K]; bool vis[K], notprime[M];
    int _n, prime[M], tot;
    
    template<typename T>
    void read(T &x) {
        int flag = 1; x = 0; char ch = getchar();
        while(ch < '0' || ch > '9') { if(ch == '-') flag = -flag; ch = getchar(); }
        while(ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar(); x *= flag;
    }
    
    ll S(int n) {
    	if(n < M) return s1[n];
    	int x = _n / n;
    	if(vis[x]) return s2[x];
    	vis[x] = true; ll &ans = s2[x];
    	ans = 1ll * n * (n + 1) / 2;
    	for(int i = 2, j; i <= n; i = j + 1)
    		j = n / (n / i), ans -= (j - i + 1) * S(n / i);
    	return ans;
    }
    
    void get_phi() {
    	phi[1] = 1;
    	for(int i = 2; i < M; ++i) {
    		if(!notprime[i]) prime[++tot] = i, phi[i] = i - 1;
    		for(int j = 1; j <= tot && i * prime[j] < M; ++j) {
    			notprime[i * prime[j]] = true;
    			if(!(i % prime[j])) { phi[i * prime[j]] = phi[i] * prime[j]; break; }
    			else phi[i * prime[j]] = phi[i] * (prime[j] - 1);
    		}
    	}
    }
    
    int main () {
    	read(_n), get_phi();
    	for(int i = 1; i < M; ++i) s1[i] = s1[i - 1] + phi[i];
    	printf("%lld
    ", S(_n));
    	return 0; 
    } 
    

    一些例题:

    • Bzoj3512 DZY Loves Math IV
    • LuoguP3768 简单的数学题
  • 相关阅读:
    C++后台开发校招面试常见问题
    算术表达式的前缀,中缀,后缀相互转换
    Redis键值对数据库的设计与实现
    C++经典面试题(最全,面中率最高)
    C++开发工程师面试题大合集
    C++中常用的设计模式
    C++中const修饰函数,函数参数,函数返回值的作用
    C++main函数的参数介绍以及如何在main函数前执行一段代码
    Windows系统下安装tensorflow+keras深度学习环境
    第十三周学习总结
  • 原文地址:https://www.cnblogs.com/water-mi/p/10236891.html
Copyright © 2011-2022 走看看