$\pi(x)$ 表示不超过 $x$ 的素数个数。容易看出可以在 $O(N)$时间复杂度,$O(N)$ 空间复杂度离线预处理求出小于 $N$ 的素数全体。但是如果 $N=10^{12}$ 或者更大,这种做法必然是不现实的。因此下面给出高效的求解方法…
1. $\phi(x,s)$
设 $p_i$ 为第 $i$ 个素数,$\phi(x,s)$ 表示不超过 $x$ 且能不能被 $p_i,(1 \leq i \leq s)$ 整除的正整数个数。即
$$ \phi(x,s) = \sum_{n \leq x} \sum_{d|(n,p)} \mu(d) = \sum_{d|p} u(d)\lfloor \frac{x}{d} \rfloor $$
其中 $p = p_1 \cdots p_s$。
另一方面,显然我们有
$$ \phi(x,s) = \phi(x,s-1) - \phi(\frac{x}{p_s},s-1) $$
2. $\pi(x)$
我们知道一个数 $n>1$ 是素数当且仅当不存在 $p \leq \sqrt{n}$ 使得 $p \mid n$。因此当 $s \geq \pi(\sqrt{x})$ 时,
$$\phi(x,s) = \pi(x) - s + 1$$
3. $P_k(x,s)$
设 $P_k(x,s)$ 为不大于 $x$ 且正好有 $k$ 个大于 $p_s$ 的素因子(按重根计)的整数个数(方法属于 Lehmer)。进一步设 $P_0(x,s)=1$。则
$$ \phi(x,s) = \sum_{k=0} ^{\infty} P_k(x,s) $$
显然 $P_1(x,s) = \pi(x)-s$。
若 $\pi(\sqrt[3]{x}) \leq s \leq \pi(\sqrt{x})$ 则 $P_k(x,s)=0,k \geq 3$ 此时
$$ \phi(x,s) = 1 + \pi(x)-s + P_2(x,s) $$
其中
$$ P_2(x,s) = \sum_{k=s+1}^{\pi(\sqrt{x})} \left( \pi(\frac{x}{p_k}) - k + 1 \right) $$
即
$$ \phi(x,s) = \pi(x) + \sum_{k=s+1}^{\pi(\sqrt{x})} \pi(\frac{x}{p_k}) - \frac{(\pi(\sqrt{x})+s-2)(\pi(\sqrt{x})-s+1)}{2} $$
4. $\pi(x)$ 的计算公式
$$ \pi(x) = \phi(x,\pi(\sqrt[3]{x})) - \sum_{k=\pi(\sqrt[3]{x})+1}^{\pi(\sqrt{x})} \pi(\frac{x}{p_k}) + \frac{(\pi(\sqrt{x})+\pi(\sqrt[3]{x})-2)(\pi(\sqrt{x})-\pi(\sqrt[3]{x})+1)}{2} $$
因此问题最终转化成求 $\phi(x,\pi(\sqrt[3]{x}))$。它可以利用
- $\phi(x,0) = \lfloor x \rfloor$
- $\phi(x,s) = \phi(x,s-1) - \phi(\frac{x}{p_s},s-1)$
至此问题貌似就这么解决了。但是由于这个递归会使得程序效率大大降低,因此需要一些预处理操作。
- 若 $x<p_s$ 则 $\phi(x,s) = 1$
- 给定一个小整数M,预处理出 $\phi(x,s)$,其中 $x < PS=p_1 \cdots p_s,s<=M$
则 $\phi(x,s) = \phi(x\%PS,s) + \lfloor \frac{x}{PS} \rfloor \phi(PS,s) $
5. 更为实用的lehmer计算公式
令 $s = \pi(\sqrt[4]{x}),t=\pi(\sqrt[2]{x}),m=\pi(\sqrt[3]{x})$。则
$$ \begin{align}
\phi(x,s) &= 1 + \pi(x) - s + P_2(x,s) + P_3(x,s) \\
&= 1+ \pi(x) - s + \sum_{k=s+1}^{t} \left( \pi(\frac{x}{p_k}) - k +1 \right) + \sum_{k=s+1}^{m} P_2(\frac{x}{p_k},k-1) \\
&= \pi(x) - \frac{(t-s+1)(t+s-2)}{2} + \sum_{k=s+1}^{t} \pi(\frac{x}{p_k}) \sum_{k=s+1}^{m} P_2(\frac{x}{p_k},k-1)
\end{align}
$$
即
$$ \pi(x) = \phi(x,s) + \frac{(t-s+1)(t+s-2)}{2} -\sum_{k=s+1}^{t} \pi(\frac{x}{p_k}) - \sum_{k=s+1}^{m} P_2(\frac{x}{p_k},k-1)
$$
6. 例题:Codeforce 665F
1 | const int N = 5e6+2; |
简洁的DP做法。
我们令 $dp(x,s) = \phi(x,s)-s+1$ 它的意义是,$2~x$ 中被前$s$ 个素数筛完后的伪素数个数。因此我们有 $dp(0,0)=0,dp(x,0)=x-1,x>1,dp(x,\pi(\sqrt{x})) = \pi(x)$ 且有状态转移
$$ dp(x,s) = dp(x,s-1)-dp(\frac{x}{p_s},s-1)+s-1 $$
因为 $dp(p_{s-1},s) = s-1$,最后一项可以写成 $dp(p_{s-1},s)$。虽然上面需要二维数组,但是实际上我们可以优化成一维数组的情绪。因为
$$ dp(x,s) = dp(x,s-1)-dp(\frac{x}{p_s},s-1)+ dp(p_{s-1},s) $$
另外我们也不可能开 $O(n)$ 的数组,但是可以利用一种黑科技开 $O(\sqrt{n})$ 的数组即可达到我们的目的。
即我们用 $L[x]$ 表示 $dp(x,s)$ 用 $R[x]$ 表示 $dp(\frac{n}{x},s)$。
但是上述做法的复杂度为 $\frac{O(n)}{\log n}$。但是常数特别小,代码十分简洁。也可以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
40LL sqrt2(LL x){
LL r = (LL)sqrt(x-0.1);
while(r*r<=x) ++r;
return r-1;
}
LL sqrt3(LL x){
LL r = (LL)cbrt(x-0.1);
while(r*r*r<=x) ++r;
return r-1;
}
const int N = 1e6+2;
LL L[N],R[N];
LL getans(LL n){ // n < 1e12
LL rn = sqrt2(n);
for(LL i=1;i<=rn;++i) R[i]=n/i-1;
LL ln = n/(rn+1)+1;
for(LL i=1;i<=ln;++i) L[i]=i-1;
for(LL p=2;p<=rn;++p){
if(L[p]==L[p-1]) continue;
for(LL i=1,tn=min(n/(p*p),rn);i<=tn;++i){
R[i] -= (i*p<=rn?R[i*p]:L[n/(i*p)])-L[p-1];
}
for(LL i=ln;i>=p*p;--i){
L[i] -= L[i/p]-L[p-1];
}
}
LL ans = L[sqrt3(n)];
for(LL p=2;p<=rn;++p){
if(L[p] == L[p-1]) continue;
ans += R[p]-L[p];
}
return ans;
}
int main(){
LL n;
while(cin>>n){
cout<<getans(n)<<endl;
}
return 0;
}