pxp
Time Limit: 2000 ms Memory Limit: 512 MB
Description
给定 \(n\), 求\(\sum\limits_{p,q∈primes}[pq≤n]\)
(其中 primes 表示全体质数组成的集合)
(其中 [expression] 当 expression 为真时值为 \(1\), 否则为 \(0\))
Input
一行一个数 \(n\)
Output
一行一个数表示答案
Sample Input
sample 1:
1
sample 2:
5
sample 3:
10
Sample Output
sample 1:
0
sample 2:
1
sample 3:
6
HINT
对于 20% 的数据, \(1≤n≤10^8\)
对于 100% 的数据 , \(1≤n≤10^{11}\)
Solution
(所以为什么学长强行出了一道科普性质的题啊qwq被科普到了qwq)
- 20%
线性筛爆搞一下就好了
- 100%
于是乎这里就科普了一种十分神秘的筛法了qwq
首先先想想怎么去求我们的\(ans\)
用\(pcnt(i)\)表示小于等于\(i\)的素数的个数,用\(cnt\)表示小于等于\(\sqrt{n}\)的素数的个数,\(P\)为素数列表,那么
具体一点的话就是,如果两个素数\(p\)和\(q\)的乘积小于等于\(n\),那么这两个素数中必定有一个小于等于\(\sqrt{n}\),所以我们只要枚举小于等于\(\sqrt{n}\)的那个素数然后把\(pcnt(\lfloor\frac{n}{p}\rfloor)\)累加起来最后乘个\(2\)就好了,然而由于这样在枚举的时候会重复,所以我们钦定每个素数只算与大于它的素数相乘的贡献,因此要减掉\(i\)(也就是前面小于p的数中有多少个素数),最后再加上自己乘自己的\(cnt\)种情况就好了
那么现在的问题就是要求\(pcnt(i)\),我们先不考虑空间的问题
考虑把\(pcnt(i)\)筛出来,大致思路如下:
1.去掉既不是合数也不是素数的\(1\)
2.去掉小于等于i的合数
去掉\(1\)的话就是初始化\(pcnt(i)=i-1\),这个比较好搞
去掉合数的话,我们考虑用每个素数\(p\)去筛掉\(i>p\)的\(pcnt(i)\)中的一些数
考虑每个\(p\)能够筛掉的部分,应该是形如\(p*k\ (k\in [2,\lfloor \frac{i}{p} \rfloor])\)的
而因为我们是从小到大一直用不同的素数去筛,所以到用\(p\)筛的时候,\(pcnt(i)\)中包含的数的最小质因子应该是大于等于\(p\)的,所以\(p\)能够筛掉的部分,实际上应该是形如\(的最小质因子p*k\ (k的最小质因子>=p)\),也就是说实际上真正能够被\(p\)筛掉数的\(i\)应该满足:\(i>=p^2\)
再考虑一下当前那些还没有被完全筛成正确答案的\(pcnt(x)\)具有什么性质:
1.\(x>p\)
2.\(pcnt(x)\)由两部分组成,对于小于\(p\)的部分,所有的合数已经被筛完了,而大于\(p\)的部分,仅有最小 质因子\(>=p\)的数
也就是说\(pcnt(x)\)=小于\(p\)的素数个数 + \(p\)到\(x\)中最小质因子\(>=p\)的数的个数
然后回去看对于\(pcnt(i)\)来说\(p\)能够筛掉的数,我们会发现其实上面提到的\(k\)的个数其实就是\(pcnt(\lfloor \frac{i}{p}\rfloor)-pcnt(p-1)\)
具体一点的话就是:
(因为只有对于\(i>=p^2\)的\(pcnt(i)\),\(p\)才能筛掉数,所以接下来讨论的\(pcnt(i)\)都默认\(i>=p^2\))
因为\(i>=p^2\),所以\(\lfloor \frac{i}{p}\rfloor>=p\)
对于大于的情况,\(pcnt(\lfloor \frac{i}{p}\rfloor)=小于p的素数个数+p到\lfloor \frac{i}{p}\rfloor中的最小质因子>=p的数的个数\)
那么\(k\)的个数就是\(pcnt(\lfloor \frac{i}{p}\rfloor)-pcnt(p-1)\)(\(pcnt(p-1)\)已经被筛好了)
对于等于的情况,能去掉的只有\(p^2\),此时\(pcnt(\lfloor \frac{i}{p}\rfloor)=pcnt(p)\), 相减一下得到\(1\),也是成立的
综上,筛掉合数我们要做的就是:
用每个素数去筛,对于每个\(i>=p^2\),从\(pcnt(i)\)减去\(pcnt(\lfloor \frac{i}{p}\rfloor)-pcnt(p-1)\)即可
至于素数,其实也不用预处理什么的,只要判断\(pcnt(i)\)与\(pcnt(i-1)\)是否相等即可,不等说明\(i\)是素数
然而现在的问题是,空间开不了这么大啊
我们考虑将\(pcnt(i)\)分成两部分:
\(p1(i)=pcnt(i)\)和\(p2(i)=pcnt(\lfloor \frac{n}{i}\rfloor)\),\(i\in[1,\sqrt{n}]\)
\(p1(i)\)的话直接算就好了
\(p2(i)\)的话,考虑用\(p\)来筛的情况,我们套回上面的式子稍微化一下得到:
考虑\(\lfloor \frac{n}{i*p}\rfloor\)的取值,如果说这个值小于等于\(\sqrt{n}\)那么\(pcnt(\lfloor \frac{n}{i*p}\rfloor)=p1(\lfloor \frac{n}{i*p}\rfloor)\),否则\(pcnt(\lfloor \frac{n}{i*p}\rfloor)=p2(i*p)\)
那就直接在枚举的时候判断一下就好了
注意最后统计\(ans\)的时候因为我们要调用的是\(()pcnt(\lfloor \frac{n}{P_i}\rfloor)(\lfloor \frac{n}{P_i}\rfloor>=\sqrt{n})\),所以应该是用\(p2(\lfloor \frac{n}{\lfloor \frac{n}{P_i}\rfloor}\rfloor)\)
代码大概长这个样子
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
const int MAXN=1e6+10;
ll p1[MAXN],p2[MAXN],lis[MAXN];
ll n,m,sq,ans,cnt;
int main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
#endif
scanf("%lld",&n);
sq=sqrt(n);
for (int p=2;p<=sq;++p) p2[p]=n/p-1,p1[p]=p-1;
for (ll p=2;p<=sq;++p)
if (p1[p]!=p1[p-1]){
lis[++cnt]=p;
for (ll i=1;i<=sq&&1LL*p*p<=n/i;++i){
if (n/(1LL*p*i)>sq)
p2[i]-=p2[p*i]-p1[p-1];
else
p2[i]-=p1[n/(1LL*p*i)]-p1[p-1];
}
for (int i=sq;i>=p*p;--i){
p1[i]-=p1[i/p]-p1[p-1];
}
}
ans=0;
for (int i=1;i<=cnt;++i)
ans+=p2[n/(n/lis[i])]-i;
printf("%lld\n",ans*2+cnt);
}