zoukankan      html  css  js  c++  java
  • 亚线性筛

    所谓的亚线性筛,是指一类用低于线性复杂度求出积性函数前缀和的方法。

    杜教筛

    杜教筛可以在(O(n^{frac{2}{3}}))的时间复杂度求出(sqrt{n}) 个点值,原理和实现都比较简单。

    原理与实现

    对于数论函数(f),要求计算(S(n)=sumlimits_{i=1}^{n}{f(i)})。对于任意的数论函数(g),有

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

    如果可以快速求出(sumlimits_{i=1}^n{(g*f)(i)})以及(g)(sqrt{n})个前缀和,再用数论分块递归求解即可得到(S(n))。因此关键是找到合适的(g),使得(g*f)可以快速求和。

    假设求和计算复杂度为(O(1)),直接计算时间复杂度为(O(int_0^{n^{frac{1}{2}}}{sqrt{frac{n}{x}}=O(n^{frac{3}{4}})})),如果使用线性筛预处理前(n^{frac{2}{3}})项的结果,时间复杂度就变为(O(int_0^{n^{frac{1}{3}}}{sqrt{frac{n}{x}}=O(n^{frac{2}{3}})})),可以处理(10^{11})级别的数据。

    常用的乘性函数的求和:

    • 莫比乌斯函数:(mu * 1=epsilon)(epsilon(n) = [n=1])

    [S(n)=1-sum_{i=2}^{n}{S(lfloorfrac{n}{i} floor)} ]

    • 欧拉函数:(varphi * 1 = operatorname{id})(operatorname{id}(n)=n)

    [S(n)=frac{1}{2}n(n+1)-sum_{i=2}^{n}{S(lfloorfrac{n}{i} floor)} ]

    • 约数个数:(operatorname{d} * mu=1)(需要(mu)前缀和)

    [S(n)=n-sum_{i=2}^{n}{mu(i)S(lfloorfrac{n}{i} floor)} ]

    Powerful Number筛

    Powerful Number(下面简称PN)筛可以说是杜教筛的扩展,可以结合杜教筛求一些积性函数的前缀和。

    要求:

    假设要求(F(n)=sumlimits_{i=1}^{n}{f(i)}),如果存在函数(g)满足:

    • (g)是积性函数
    • (g)前缀和(G)容易求得
    • 对于质数(p)(g(p)=f(p))

    那么就可以较快的求出(F(n))

    原理与实现

    (f)可以写成(f=h*g),那么有((h)(g)都是积性函数)

    [egin{align} F(n) &=sumlimits_{i=1}^{n}{(g*h)(i)} \ &=sumlimits_{i=1}^{n}{sumlimits_{d|n}{h(d)g(frac{n}{d})}} \ &= sumlimits_{d=1}^{n}{h(d)sumlimits_{i=1}^{lfloorfrac{n}{d} floor}{g(i)}} \ &= sumlimits_{d=1}^{n}{h(d)G(lfloorfrac{n}{d} floor)} end{align} ]

    如果可以使得(p)为质数时有(h(p)=0),即(f(p)=h(p)+g(p)=g(p))时,就有当(d)中含有次数为1的质因子时上面式子中(h(d)=0),相当于把含有次数为1质因子的(d)全部都“筛掉”了。将所有不含1次质因子的数称为PN,就有

    [F(n)= sumlimits_{d=1, d { m is PN}}^{n}{h(d)G(lfloorfrac{n}{d} floor)} ]

    这是很强力的筛选,因为(n)以内的PN只有(O(sqrt{n}))个。

    PN筛分为以下几个步骤:

    1. 构造(g)

    注意构造的(g)要是积性函数,且满足(f(p)=g(p)),比如

    • (f(p^c)=p),那么构造(g(x)=x)

    • (f(p^c)=p^c(p^c-1)),那么构造(g(x)=id(x)varphi(x))

    (g)的前缀和(G)要好求,注意到需要的是(lfloorfrac{n}{i} floor)处的(G)值,所以常用杜教筛。

    1. 计算(h(p^c))

    知道了(g)(f)就可以求出(h),由

    [f(p^c)=sum_{i=0}^{c}{h(p^i)g(p^{c-i})} ]

    可得

    [h(p^c)=h(p^c)g(1)=f(p^c)-sum_{i=0}^{c-1}{h(p^i)g(p^{c-i})} ]

    可以枚举(p)(c)递推预处理出来。

    1. 搜索PN,过程中结果累和起来

    直接枚举质因子以及次数搜索,由于PN只有(O(sqrt{n}))个,只用搜索(O(sqrt{n}))次。全部结果加起来就是答案。

    如果处理(G)的时间复杂度为(O(n^{alpha})),那么总时间复杂度为(O(max(sqrt{n},n^{alpha})))

    例题

    题目:P5325 【模板】Min_25筛

    (f(p^c)=p^c(p^c-1)),那么构造(g(x)=id(x)varphi(x))

    可以发现((idcdotvarphi)*id=id^2),所以可以使用杜教筛,即

    [G(n)=sumlimits_{i=1}^{n}{i^2}-sum_{i=2}^{n}{i cdot G(lfloorfrac{n}{i} floor)} ]

    然后各种预处理(h(p^c)),直接搜索PN累加答案即可。

    #include <bits/stdc++.h>
    
    #define endl '
    '
    #define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
    #define mp make_pair
    #define seteps(N) fixed << setprecision(N) 
    typedef long long ll;
    
    using namespace std;
    /*-----------------------------------------------------------------*/
    
    ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
    #define INF 0x3f3f3f3f
    
    const int N = 6e6 + 10;
    const ll MX = 1e10 + 10;
    const int M = 1e9 + 7;
    const double eps = 1e-5;
    
    
    ll pri[N];
    bool isnp[N];
    int cnt;
    
    inline ll qpow(ll a, ll b, ll m) {
    	ll res = 1;
    	while(b) {
    		if(b & 1) res = (res * a) % m;
    		a = (a * a) % m;
    		b = b >> 1;
    	}
    	return res;
    }
    
    namespace PNS {
    	map<ll, ll> mpG;
    	ll g[N], G[N];
    	ll h[N][35];
    	ll global_n, ans;
    	ll r6, r2;
    	void init() { // 预处理素数和h[p^c]
    		r2 = qpow(2, M - 2, M);
    		r6 = qpow(6, M - 2, M);
    		isnp[1] = 1;
    		g[1] = 1;
    		for(int i = 2; i < N; i++) {
    			if(!isnp[i]) {
    				pri[cnt++] = i;
    				g[i] = 1ll * i * (i - 1) % M;
    			}
    			for(int j = 0; j < cnt && i * pri[j] < N; j++) {
    				isnp[i * pri[j]] = 1;
    				if(i % pri[j] == 0) {
    					g[i * pri[j]] = g[i] * pri[j] % M * pri[j] % M;
    					break;
    				}
    				g[i * pri[j]] = g[i] * g[pri[j]] % M;
    			}
    		}
    		for(int i = 1; i < N; i++) G[i] = (g[i] + G[i - 1]) % M;
    		for(int i = 0; i < cnt; i++) {
    			h[i][0] = 1;
    			h[i][1] = 0;
    		}
    		for(int i = 0; i < cnt; i++) {
    			ll pp = pri[i] * pri[i], rp = qpow(pri[i], M - 2, M);
    			for(int c = 2; c < 35; c++) {
    				ll pw = pp % M;
    				h[i][c] = pw * (pw - 1) % M;
    				for(int k = 0; k < c; k++) {
    					h[i][c] = (h[i][c] - h[i][k] * pw % M * pw % M * (pri[i] - 1) % M * rp % M + M) % M;
    					pw = pw * rp % M;
    				}
    				if(pp > MX / pri[i]) break;
    				pp = pp * pri[i];
    			}
    		}
    	}
    
    	ll sum(ll n) { // 杜教筛预处理
    		if(n < N) return G[n];
    		if(mpG.count(n)) return mpG[n];
    		ll res = n % M * (n % M + 1) % M * (2 * n % M + 1) % M * r6 % M;
    		ll i = 2;
    		while(i <= n) {
    			ll j = n / (n / i);
    			res = (res - (j - i + 1) % M * ((i + j) % M) % M * r2 % M * sum(n / i) % M + M) % M;
    			i = j + 1;
    		}
    		mpG[n] = res;
    		return res;
    	}
    
    	void dfs(int p, ll hv, ll v) { // 搜索PN, hv为h函数值,v为当前找到的PN
    		ans = (ans + hv * sum(global_n / v) % M) % M;
    		for(int i = p; i < cnt && v <= global_n / pri[i] / pri[i]; i++) {
    			ll tv = v * pri[i] * pri[i];
    			for(int c = 2; ; c++) {
    				dfs(i + 1, hv * h[i][c] % M, tv);
    				if(tv > global_n / pri[i]) break;
    				tv = tv * pri[i];
    			}
    		}
    	}
    	
    	ll solve(ll n) {
    		global_n = n;
    		sum(n); 
    		ans = 0;
    		dfs(0, 1, 1);
    		return ans;
    	}
    }
    
    int main() {
    	IOS;
    	PNS::init();
    	ll n;
    	cin >> n;
    	cout << PNS::solve(n) << endl;
    }
    

    min25筛

    min25筛 学习笔记

  • 相关阅读:
    adb shell top
    数据清洗的方法
    Devices Tree加载流程
    Android驱动之设备树简介
    序列模式挖掘综述
    python 实现kmeans聚类
    numpy中sum(axis=0)和axis=1的计算原理
    win7 VMware下安装centos和Ubuntu共存
    python数据标准化
    python 用PIL Matplotlib处理图像的基本操作
  • 原文地址:https://www.cnblogs.com/limil/p/15227622.html
Copyright © 2011-2022 走看看