杜教筛
参考资料:
https://blog.csdn.net/Ike940067893/article/details/84781307
https://www.luogu.com.cn/problemnew/solution/P4213
前置知识:
主要内容:
杜教筛
杜教筛能用低于线性的时间复杂度求积性函数前缀和。
比如要求积性函数 (f) 的前缀和 (sum(n)=sumlimits_{i=1}^nf_i)。
需要找一个辅助积性函数 (g),考虑 ((f*g))((*) 是狄利克雷卷积,不懂自学)的前缀和 (sum'):
于是得到了最主要的式子,单独提出来:
如果 (g) 找得合适,大部分时候 (g(1)=1),而 (sumlimits_{i=1}^n(f*g)(i)) 和 (sumlimits_{i=1}^ng(i)) 可以 (Theta(1)) 算,而 (sumlimits_{d=2}^ng(d)sum(lfloorfrac nd floor)) 可以整除分块算。
于是直接递归求 (sum(n)) 的时间复杂度为 (Theta(n^{frac{3}{4}}))。
证明
设求出 $sum(n)$ 的时间复杂度为 $Theta(T(n))$:虽然等式步步是大于,但是如果记忆化 (sum(n)),时间复杂度就是 (Theta(n^{frac{3}{4}}))。
优化:
线性筛求出前 (n^{frac{2}{3}}) 个 (sum(i)),然后再杜教筛剩下的,优化后的时间复杂度为 (Theta(n^{frac{2}{3}}))。
证明
设求出 $sum(n)$ 的时间复杂度为 $Theta(T(n))$:经典例题
【模板】杜教筛(Sum)
(T) 组测试数据,给定 (n),求 (sumlimits_{i=1}^nvarphi(i)) 和 (sumlimits_{i=1}^nmu(i))。
数据范围:(1le Tle 10),(1le nle 2^{31}-1)。
求 (mu) 的前缀和:
此时 (f=mu),如果取 (g=1),那么 (f*g=mu*1=epsilon)。
同时满足:
- (g(1)=1)。
- (sumlimits_{i=1}^n(f*g)(i)=1) 可以 (Theta(1)) 算出。
- (sumlimits_{i=1}^ng(i)=n) 可以 (Theta(1)) 算出。
所以有:
右边分块递归求解,(sum) 记忆化,时间复杂度 (Theta(n^{frac{2}{3}}))。
( exttt{Code})
hash<int,lng> Mu;
il lng DuMu(re int n){
if(n<=N) return mu[n];
if(Mu[n]) return Mu[n];
re lng res=1ll;
for(re int l=2,r;l<=n;l=r+1)
r=n/(n/l),res-=(r-l+1)*DuMu(n/l);
return Mu[n]=res;
}
求 (varphi) 的前缀和:
此时 (f=varphi),如果取 (g=1),那么 (f*g=varphi*1=ID)。
同时满足:
- (g(1)=1)。
- (sumlimits_{i=1}^n(f*g)(i)=frac{n(n+1)}{2}) 可以 (Theta(1)) 算出。
- (sumlimits_{i=1}^ng(i)=n) 可以 (Theta(1)) 算出。
所以有:
右边分块递归求解,(sum) 记忆化,时间复杂度 (Theta(n^{frac{2}{3}}))。
( exttt{Code})
hash<int,lng> Phi;
il lng DuPhi(re int n){
if(n<=N) return phi[n];
if(Phi[n]) return Phi[n];
re lng res=1ll*n*(n+1)/2;
for(re int l=2,r;l<=n;l=r+1)
r=n/(n/l),res-=(r-l+1)*DuPhi(n/l);
return Phi[n]=res;
}
$ exttt{Code}$
#include <bits/stdc++.h>
using namespace std;
//&Start
#define inf 0x3f3f3f3f
#define re register
#define il inline
#define hash unordered_map
typedef long long lng;
typedef unsigned long long ulng;
typedef vector<int> veci;
//&Data
#define N 5000000
//&Sieve
bitset<N+10> np;
int p[N+10];
lng mu[N+10],phi[N+10];
il void Sieve(){
np[1]=true;
mu[1]=1,phi[1]=1;
re int cnt=0;
for(re int i=2;i<=N;i++){
if(!np[i]) p[++cnt]=i,mu[i]=-1,phi[i]=i-1;
for(re int j=1;j<=cnt&&i*p[j]<=N;j++){
np[i*p[j]]=1;
if(i%p[j]==0){mu[i*p[j]]=0,phi[i*p[j]]=phi[i]*p[j];break;}
mu[i*p[j]]=-mu[i],phi[i*p[j]]=phi[i]*phi[p[j]];
}
}
for(re int i=2;i<=N;i++) mu[i]+=mu[i-1],phi[i]+=phi[i-1];
}
//&Du-Sieve
hash<int,lng> Mu,Phi; //记忆化sum
il lng DuPhi(re int n){
if(n<=N) return phi[n];
if(Phi[n]) return Phi[n];
re lng res=1ll*n*(n+1)/2;
for(re int l=2,r;l<=n;l=r+1)
r=n/(n/l),res-=(r-l+1)*DuPhi(n/l);
return Phi[n]=res;
}
il lng DuMu(re int n){
if(n<=N) return mu[n];
if(Mu[n]) return Mu[n];
re lng res=1ll;
for(re int l=2,r;l<=n;l=r+1)
r=n/(n/l),res-=(r-l+1)*DuMu(n/l);
return Mu[n]=res;
}
//&Main
int main(){
Sieve();
re int n,t;
scanf("%d",&t);
for(re int i=1;i<=t;i++){
scanf("%d",&n);
printf("%lld %lld
",DuPhi(n),DuMu(n));
}
return 0;
}
毒瘤的数学题
给定 (n) 和 (p),求
[left(sumlimits_{i=1}^nsumlimits_{j=1}^nijgcd(i,j) ight)mod p ]
数据范围:(1le nle color{red}{10^{10}}),(5 imes10^{8}le ple 1.1 imes 10^{9})。
稍微改了一下题目抬头,因为对这题的毒瘤深有体会。如果你看不到这道题的前半段推导,说明你没有学好莫比乌斯反演。
第一部分:普通の推导
设 (S(n)=sumlimits_{i=1}^ni=frac{n(n+1)}{2}),所以
第二部分:代换
将 (T=dk) 带入:
将公式 (mu*ID=varphi) 带入得:
第三部分:杜教卷积
这里给出其中一种方法,可能不是最优的:
将整个 (T^2varphi(T)) 提出来杜教,即令 (f(n)=n^2varphi(n))。
为了防止遗忘,把最重要的套路式再拿出来遛一下:
为了抵消掉 (f) 中的 (n^2),(g) 应该等于 (n^2)。
则有:
将公式 (varphi*1=ID) 带入得:
于是便满足:
- (g(1)=1)。
- (sumlimits_{i=1}^n(f*g)(i)=S(n)^2) 可以 (Theta(1)) 算出。
- (sumlimits_{i=1}^ng(i)=frac{n(n+1)(2n+1)}{6}) 可以 (Theta(1)) 算出。
所以可以杜教筛得:
然后再看原式:
就可以分块加杜教解决了,复杂度 (Theta(n^{frac {2}{3}}))。
代码:要取模,要 ( exttt{long long})。
$ exttt{Code}$
#include <bits/stdc++.h>
using namespace std;
//&Start
#define inf 0x3f3f3f3f
#define re register
#define il inline
#define hash unordered_map
typedef long long lng;
typedef unsigned long long ulng;
typedef vector<int> veci;
//&Data
#define N 5000000
int m;
//&Pow
il int Pow(re int a,re int x){ //用于求逆元
re int res=1;
for(;x;a=1ll*a*a%m,x>>=1)if(x&1) res=1ll*res*a%m;
return res;
}
int inv;
//&Sum
il int sum(re lng x){x%=m;return 1ll*x*(x+1)/2%m;} //即S
il int sum2(re lng x){x%=m;return 1ll*x*(x+1)%m*(2*x+1)%m*inv%m;} //即n(n+1)(2n+1)/6
//&Sieve
bitset<N+10> np;
int p[N+10],phi[N+10],f[N+10];
il void Sieve(){ //优化筛
np[1]=true,f[1]=phi[1]=1;
re int cnt=0;
for(re int i=2;i<=N;i++){
if(!np[i]) p[++cnt]=i,phi[i]=i-1;
f[i]=1ll*i*i%m*phi[i]%m;
for(re int j=1;j<=cnt&&i*p[j]<=N;j++){
np[i*p[j]]=1;
if(i%p[j]==0){phi[i*p[j]]=phi[i]*p[j];break;}
phi[i*p[j]]=phi[i]*phi[p[j]];
}
}
for(re int i=2;i<=N;i++) (f[i]+=f[i-1])%=m;
}
//&Du-Sieve0-------------->杜教筛
hash<lng,int> F;
il int DuF(re lng n){
if(n<=N) return f[n];
if(F[n]) return F[n];
re int res=sum(n); res=1ll*res*res%m;
for(re lng l=2,r;l<=n;l=r+1)
r=n/(n/l),(res-=1ll*(sum2(r)-sum2(l-1))%m*DuF(n/l)%m)%=m;
return F[n]=(res+m)%m;
}
//&Main
int main(){
re lng n;
scanf("%d%lld",&m,&n);
inv=Pow(6,m-2),Sieve();
re int ans=0,tmp;
for(re lng l=1,r;l<=n;l=r+1){
r=n/(n/l),tmp=sum(n/l);
(ans+=1ll*(DuF(r)-DuF(l-1))%m*tmp%m*tmp%m)%=m; //分块加杜教
}
printf("%d
",(ans+m)%m);
return 0;
}
如果有感悟,会更新。
祝大家学习愉快!