区间筛
例题:每次给一对 $l,r$ ,求区间内的质数有多少个.
范围是 $l\le r\le10^{12},b-a\le10^6$.
首先要明白一个常见的道理: 区间 $[a,b]$ 内最大的质因数是 $\sqrt b$ .所以我们要先打好1e6内的表,然后利用坐标偏移再打好lr的表即可.
看到 $10^{12}$ 的数据范围要想到区间筛.因为数据范围这么大的话,一般要用到,”一个数的最小质因数不会超过 $\sqrt n$ “这个结论,然后先质数筛,再对每个数进行质因数分解O(logn).然后利用唯一分解定理把约数组合拼上去即可.
Min_25筛
Min_25筛用于解决积性函数前缀和的问题(事实上不是积性函数也能写,那是后话),因为常数小,跑的比杜教筛还快.
Min_25构成主要是一个神仙dp,原理是下面四个公式,代码略,直接抄题解,这不是这篇文章的重点.
构造一个完全积性函数
只需要满足在质数点和 $f(x)$ 一样即可,质数次方不需要满足性质.
本篇侧重于对Min_25筛中完全积性函数的构造.
数论分块的结果是有 $2\sqrt n$ 个取值,所以对于 $1e10$ 大小的数据要开 $2e5$ 大小的数组,,,对于1e11要开7e5大小的数组,直接1e6准没错,再次强调.
要求
- $f$ 是积性函数
- $f(p^k)$ 能快速计算
大致流程
先把质数点算出来看是什么,即 $f(p)=xxx$ ,得到一个多项式.
然后把 $f(p)$ 按照多项式的形式拆开,比如 $f(p)=p-1$ 会被拆成 和 .
然后对拆出来的g分别求和(这个时候不要管正负号): $sum=\sum_{i=2}^ng(i)$ .
剩下的全是Min_25板子,不过需要一点小改动:
- 正负号的作用体现在
S(n) 的计算中,也就是 $res=\pm g_i(x)$ 在这里体现正负号和倍数(如果有)
- 函数拆分的作用体现在
solve(n) 函数中,具体地,只需要修改后面两个for循环即可.
- 一定要注意在算g函数的时候使用的质数应该是
p[i] 不是 p[j] ,Flu在调欧拉函数因为这里没注意到调了俩小时.
luogu P5325
这个东西能拆成两部分,前后两部分都是完全积性的,计算的时候拼一起就行了.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| long long sq,n; const int mod=1000000007; long long id1[200010],id2[200010],w[200010]; int vis[200010]; long long p[200010],ptop=0; void sieve(int n){ int tmp; for(int i=2;i<=n;++i){ if(!vis[i]){ vis[i]=-(++ptop); p[ptop]=i; } for(int j=1;j<=ptop&&i*p[j]<=n;++j){ vis[i*p[j]]=p[j]; if(i%p[j]==0){ break; } } } } template<typename T1,typename T2,typename T3> T1 qp(T1 b,T2 po,T3 p){ T1 res(1); while(po>0){ if(po&1) res=res*b%p; b=b*b%p; po>>=1; } return res; } long long getid(long long x){ if(x<=sq)return id1[x]; return id2[n/x]; } long long m,inv2,inv6; long long g1[200010],g2[200010],sum1[200010],sum2[200010];
long long f1(long long x){ x%=mod; return x*(x+1)%mod*inv2%mod; } long long f2(long long x){ x%=mod; return x*(x+1)%mod*(2*x+1)%mod*inv6%mod; } long long S(long long n,long long j){ if(p[j]>n)return 0; i64 res=(g2[getid(n)]-g1[getid(n)]-sum2[j]+sum1[j])%mod; for(int k=j+1;k<=ptop&&p[k]*p[k]<=n;++k){ for(i64 e=1,pk=p[k];pk<=n;++e,pk*=p[k]){ res=(res+(pk%mod*(pk%mod-1)%mod*(S(n/pk,k)+(e>1))%mod))%mod; } } return (res%mod+mod)%mod; } long long solve(long long n){ sq=sqrt(n),inv2=qp((long long)2,mod-2,mod),inv6=qp((long long)6,mod-2,mod); sieve(sq); int m=0; for(i64 i=1;i<=ptop;++i){ sum1[i]=(sum1[i-1]+p[i])%mod; sum2[i]=(sum2[i-1]+p[i]*p[i])%mod; } for(i64 l=1,r;l<=n;l=r+1){ r=(n/(n/l)); w[++m]=n/l; g1[m]=f1(w[m])-1; g2[m]=f2(w[m])-1; if(w[m]<=sq)id1[w[m]]=m; else id2[n/w[m]]=m; } for(int i=1;i<=ptop;++i){ for(int j=1;j<=m&&p[i]*p[i]<=w[j];++j){ g1[j]=(g1[j]-p[i]*(g1[getid(w[j]/p[i])]-sum1[i-1])%mod)%mod; g2[j]=(g2[j]-p[i]*p[i]%mod*(g2[getid(w[j]/p[i])]-sum2[i-1])%mod)%mod; } } return (S(n,0)+1)%mod; }
|
$\sum_{i=1}^n\mu(i)$
拆: $\mu(p)=-1,g(p)=-1,sum=-n+1$ .代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| long long sq,n; const int mod=1000000007; long long id1[200010],id2[200010],w[200010]; int vis[1000010]; long long p[100010],ptop=0; void sieve(int n){ int tmp; for(int i=2;i<=n;++i){ if(!vis[i]){ vis[i]=-(++ptop); p[ptop]=i; } for(int j=1;j<=ptop&&i*p[j]<=n;++j){ vis[i*p[j]]=p[j]; if(i%p[j]==0){ break; } } } } template<typename T1,typename T2,typename T3> T1 qp(T1 b,T2 po,T3 p){ T1 res(1); while(po>0){ if(po&1) res=res*b%p; b=b*b%p; po>>=1; } return res; } long long getid(long long x){ if(x<=sq)return id1[x]; return id2[n/x]; }
long long g[200010],sum[200010];
long long f(long long x){ return -x+1; }
long long S(long long n,long long j){ if(p[j]>n)return 0; i64 res=(g[getid(n)]-sum[j])%mod; for(int k=j+1;k<=ptop&&p[k]*p[k]<=n;++k){ for(i64 e=1,pk=p[k];pk<=n;++e,pk*=p[k]){ res=(res+((e>1?0:-1)*(S(n/pk,k)+(e>1))%mod))%mod; } } return (res%mod+mod)%mod; }
int main(){ fin>>n; sq=sqrt(n); sieve(sq); int m=0; for(i64 i=1;i<=ptop;++i){ sum[i]=(sum[i-1]-1)%mod; } for(i64 l=1,r;l<=n;l=r+1){ r=(n/(n/l)); w[++m]=n/l; g[m]=f(w[m]); if(w[m]<=sq)id1[w[m]]=m; else id2[n/w[m]]=m; } for(int i=1;i<=ptop;++i){ for(int j=1;j<=m&&p[i]*p[i]<=w[j];++j){ g[j]=(g[j]-1*(g[getid(w[j]/p[i])]-sum[i-1])%mod)%mod; } } fout<<(S(n,0)+1)%mod; return 0; }
|
关于为什么F(x)=1:
我们对于常数项构建的函数实际上是F(x)=1,完全积性函数,只是在匹配f(x)的时候我们加上了常数项的偏移(也就是-1).
$\sum_{i=1}^n\varphi(i)$
$f(p)=p-1$ ,拆成两部分: $g1(p)=p,g2(p)=1$ ,然后分别计算即可,提交记录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| long long sq,n; const int mod=998244353; long long id1[200010],id2[200010],w[200010]; int vis[1000010]; long long p[100010],ptop=0; void sieve(int n){ int tmp; for(int i=2;i<=n;++i){ if(!vis[i]){ vis[i]=-(++ptop); p[ptop]=i; } for(int j=1;j<=ptop&&i*p[j]<=n;++j){ vis[i*p[j]]=p[j]; if(i%p[j]==0){ break; } } } } template<typename T1,typename T2,typename T3> T1 qp(T1 b,T2 po,T3 p){ T1 res(1); while(po>0){ if(po&1) res=res*b%p; b=b*b%p; po>>=1; } return res; } long long getid(long long x){ if(x<=sq)return id1[x]; return id2[n/x]; }
long long g1[200010],g2[200010],sum1[200010],sum2[200010];
long long f1(long long x){ x%=mod; return ((x*(x+1)/2)-1)%mod; } long long f2(long long x){ x%=mod; return x-1; }
long long S(long long n,long long j){ if(p[j]>n)return 0; i64 res=((g1[getid(n)]-sum1[j])-(g2[getid(n)]-sum2[j]))%mod; for(int k=j+1;k<=ptop&&p[k]*p[k]<=n;++k){ for(i64 e=1,pk=p[k];pk<=n;++e,pk*=p[k]){ res=(res+((pk-pk/p[k])%mod*(S(n/pk,k)+(e>1))%mod))%mod; } } return (res%mod+mod)%mod; }
long long solve(long long n){ sq=sqrt(n); sieve(sq); int m=0; for(i64 i=1;i<=ptop;++i){ sum1[i]=(sum1[i-1]+p[i])%mod; sum2[i]=(sum2[i-1]+1)%mod; } for(i64 l=1,r;l<=n;l=r+1){ r=(n/(n/l)); w[++m]=n/l; g1[m]=f1(w[m]); g2[m]=f2(w[m]); if(w[m]<=sq)id1[w[m]]=m; else id2[n/w[m]]=m; } for(int i=1;i<=ptop;++i){ for(int j=1;j<=m&&p[i]*p[i]<=w[j];++j){ g1[j]=(g1[j]-p[i]*(g1[getid(w[j]/p[i])]-sum1[i-1])%mod)%mod; g2[j]=(g2[j]-(1)*(g2[getid(w[j]/p[i])]-sum2[i-1])%mod)%mod; } } return (S(n,0)+1)%mod; }
int main(){ cin>>n; cout<<solve(n); return 0; }
|
$\pi(n)=\sum_{i=1}^n[i\in prime]$
这个题略微有点不同,并未让你构造完全积性函数,只是让你寻找g函数的dp本来的意义.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| long long sq,n;
long long id1[1000010],id2[1000010],w[1000010]; int vis[1000010]; long long p[1000010],ptop=0; void sieve(int n){ for(int i=2;i<=n;++i){ if(!vis[i]){ vis[i]=-(++ptop); p[ptop]=i; } for(int j=1;j<=ptop&&i*p[j]<=n;++j){ vis[i*p[j]]=p[j]; if(i%p[j]==0){ break; } } } } long long getid(long long x){ return (x<=sq)?id1[x]:id2[n/x]; } long long g[1000010],sum[1000010]; long long f(long long x){ return x-1; } long long solve(long long n){ sq=sqrt(n); sieve(sq); int m=0; for(i64 i=1;i<=ptop;++i){ sum[i]=(sum[i-1]+1); } for(i64 l=1,r;l<=n;l=r+1){ r=(n/(n/l)); w[++m]=n/l; g[m]=f(w[m]); if(w[m]<=sq)id1[w[m]]=m; else id2[n/w[m]]=m; } for(int i=1;i<=ptop;++i){ for(int j=1;j<=m&&p[i]*p[i]<=w[j];++j){ g[j]=(g[j]-(g[getid(w[j]/p[i])]-sum[i-1])); } } return g[1]; }
|