筛质数
基础的。
// v 标记是否为合数,p 存储质数
for (int i=2; i<=N; ++i) {
v[i]||(p[++cnt]=i);
for (int j=1,t; j<=cnt&&(t=p[j]*i)<=N; ++j) {
v[t]=1;
if (i%p[j]==0) break;
}
}
原理:每个合数 \(t\) 只被其最小质因子 \(p_j\) 筛一次。
设 \(i\) 的最小质因子为 \(p_{j_0}\)。
当 \(j\le j_0\) 时,\(p_j\) 是 \(t=p_j\times i\) 的最小质因子,因此用 \(p_j\) 筛掉 \(t\);
当 \(j>j_0\) 时,\(p_{j_0}\) 是 \(t\) 的最小质因子,不应再用 \(p_j\) 去筛,直接 break 掉就好了。
根据原理,上述筛法的时间复杂度是线性的,故称为线性筛。
筛欧拉函数 \(\varphi\)
-
对于质数 \(i\):\(\varphi(i)=i-1\)。
-
对于合数 \(t=p_j\times i\)(其中 \(p_j\) 为 \(t\) 的最小质因子):
若 \(p_j\mid i\),则 \(\varphi(t)=p_j\times\varphi(i)\);
若 \(p_j\nmid i\),则 \(\varphi(t)=\varphi(p_j)\times\varphi(i)\)。
phi[1]=1;
for (int i=2; i<=N; ++i) {
v[i]||(p[++cnt]=i,phi[i]=i-1);
for (int j=1; j<=cnt&&(t=p[j]*i)<=N; ++j) {
v[t]=1;
if (i%p[j]==0) { phi[t]=p[j]*phi[i]; break; }
phi[t]=phi[p[j]]*phi[i];
}
}
筛莫比乌斯函数 \(\mu\)
同上,简单讨论一下即可。
- \(i\in\mathbb P\Longrightarrow\mu(i)=-1\)。(用 \(\mathbb P\) 表示质数集)
- \(p_j\mid i\Longrightarrow \mu(t)=0\);
\(p_j\nmid i\Longrightarrow \mu(t)=\mu(p_j)\times\mu(i)=-\mu(i)\)。
mu[1]=1;
for (int i=2; i<=N; ++i) {
v[i]||(p[++cnt]=i,mu[i]=-1);
for (int j=1; j<=cnt&&(t=p[j]*i)<=N; ++j) {
v[t]=1;
if (i%p[j]==0) break;
mu[t]=-mu[i];
}
}
筛因数个数 \(d\)
计算公式:设 \(n=\prod p_i^{k_i}\),则 \(d(n)=\prod(k_i+1)\)。
设 \(p_1\) 为最小质因子,令 \(f(n)=k_1+1\)。
- \(i\in\mathbb P\Longrightarrow f(i)=2,d(i)=2\)。
- \(p_j\mid i\Longrightarrow f(t)=f(i)+1,d(t)=\dfrac{d(i)f(t)}{f(i)}\);
\(p_j\nmid i\Longrightarrow f(t)=2,d(t)=2d(i)\)。
d[1]=1;
for (int i=2; i<=N; ++i) {
v[i]||(p[++cnt]=i,f[i]=2,d[i]=2);
for (int j=1,t; j<=cnt&&(t=p[j]*i)<=N; ++j) {
v[t]=1;
if (i%p[j]==0) { f[t]=f[i]+1,d[t]=d[i]/f[i]*f[t]; break; }
f[t]=2,d[t]=(d[i]<<1);
}
}
筛因数和 \(\sigma\)
计算公式:设 \(n=\prod p_i^{k_i}\),则 \(\sigma(n)=\prod(1+p_i+p_i^2+\cdots+p_i^{k_i})\)。
设 \(p_1\) 为最小质因子,令 \(g(n)=1+p_1+p_1^2+\cdots+p_1^{k_1}\)。
- \(i\in\mathbb P\Longrightarrow g(i)=i+1,\sigma(i)=i+1\)。
- \(p_j\mid i\Longrightarrow g(t)=p_j\times g(i)+1,f(t)=\dfrac{f(i)g(t)}{g(i)}\);
\(p_j\nmid i\Longrightarrow g(t)=p_j+1,\sigma(t)=\sigma(p_j)\times\sigma(i)\)。
s[1]=1;
for (int i=2; i<=N; ++i) {
v[i]||(p[++cnt]=i,g[i]=i+1,s[i]=i+1);
for (int j=1,t; j<=cnt&&(t=p[j]*i)<=N; ++j) {
v[t]=1;
if (i%p[j]==0) { g[t]=p[j]*g[i]+1,s[t]=s[i]/g[i]*g[t]; break; }
g[t]=p[j]+1,s[t]=s[p[j]]*s[i];
}
}
筛一般的积性函数
先鸽着,估计暂时也用不到。
筛 \(k\) 次幂
具体地说,给定 \(n,k,P\),求 \(1^k\sim n^k\) 的值,对 \(P\) 取模。
令 \(F(x)=x^k\),那么 \(F\) 是个积性函数,所以也可以线性筛。
但是对于质数 \(i\),我们只能做到用快速幂 \(O(\log k)\) 求出 \(i^k\bmod p\)。
\(n\) 以内的质数个数 \(\pi(n)\approx\dfrac n{\ln n}\),因此筛法的时间复杂度为 \(O\left(n+\dfrac{n\log k}{\ln n}\right)\)。
给定 \(n,m,k\),计算
\[\sum_{i=1}^n\sum_{j=1}^m\gcd(i,j)^k \]对 \(10^9+7\) 取模的结果。
每个测试点包含 \(T\) 组数据,它们共用相同的 \(k\)。\(1\le T\le 2\times10^3,1\le n,m,k\le 5\times10^6.\)
推柿子的过程很常规,不写了,最后的结果是
关键在于括号里的部分怎么求。
理论上说,它是 \(f(x)=x^k\) 与 \(\mu\) 这两个积性函数的狄利克雷卷积,所以也是积性函数,应该可以用线性筛预处理。
设 \(g(n)=\sum\limits_{d\mid n}d^k\mu(\tfrac nd)\)。
对于 \(i\in\mathbb P\),显然有 \(g(i)=i^k-1\)。
对于合数 \(t\),设 \(t=p\times q\),其中 \(p\) 是 \(t\) 的最小质因子。
若 \(p\nmid q\),根据积性即可得到 \(g(t)=g(p)\times g(q)\)。
若 \(p\mid q\),我们可以得出 \(g(t)=p^k\times g(q)\)。推导过程留作习题。
至此可以用线性筛求 \(g\)。质数位置直接用快速幂计算。
时间复杂度 \(O\left(n+\dfrac{n\log k}{\ln n}+T\sqrt n\right)\)。
#include<stdio.h>
const int N=5000005,P=1e9+7;
int T,n,m,k,cnt,t,ans,p[500005],s[N],g[N]; bool v[N];
inline int min(int x,int y) { return x<y?x:y; }
int power(int x,int y) {
int s=1;
while (y) (y&1)&&(s=1ll*s*x%P),x=1ll*x*x%P,y>>=1;
return s;
}
int main() {
scanf("%d%d",&T,&k),n=5000000,s[1]=g[1]=1;
for (int i=2; i<=n; ++i) { // 筛子
v[i]||(p[++cnt]=i,g[i]=power(i,k)-1);
for (int j=1; j<=cnt&&(t=p[j]*i)<=n; ++j) {
v[t]=1;
if (i%p[j]==0) { g[t]=1ll*(g[p[j]]+1)*g[i]%P; break; }
g[t]=1ll*g[p[j]]*g[i]%P;
}
(s[i]=s[i-1]+g[i])>=P&&(s[i]-=P); // 前缀和
}
while (T--) {
scanf("%d%d",&n,&m),n>m&&(t=n,n=m,m=t);
for (int l=1,r,t1,t2; l<=n; l=r+1) // 整除分块
r=min(n/(t1=n/l),m/(t2=m/l)),
ans=(1ll*t1*t2%P*(s[r]-s[l-1]+P)+ans)%P;
printf("%d\n",ans),ans=0;
}
return 0;
}