一种质数筛法
- 例题:Luogu7884
考虑 min_25 筛,可以做到 \(O(\frac{n^{0.75}}{\log n})\)。
但是 \(n\le 10^{13}\),无法通过。
考虑值域分治,使用树状数组处理 \(1\sim B_1\) 的质数前缀个数。可以把筛质数改成 \(O(n\log \log n)\) 筛,会方便一点,每次可以顺便地扫描每个最小质因子是当前枚举质数的数。
时间 \(O(B_1\log B_1+ \frac{n}{\sqrt B_1 \log n})\),取 \(B_1=\frac{n^{\frac 23}}{\log ^{\frac 43}n}\) 可以做到 \(O(\frac{n^{\frac 23}}{\log ^{\frac 13}n})\)。
考虑 \(1\sim B_2\) 直接暴力做,\(B_2+1\sim B_1\) 用树状数组。
取 \(B_2=\sqrt[6]n,\space B_1=(\frac n{\log n})^{\frac 23}\) 时做到 \(O((\frac n{\log n})^{\frac 23})\)。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define pir pair<ll,ll>
#define fi first
#define se second
#define mkp make_pair
#define pb push_back
using namespace std;
const ll maxn=4e6+10;
ll n,g[maxn<<1],id1[maxn],id2[maxn],sq,w[maxn<<1],len,sum[maxn],cnt;
int tree[maxn*10];
bool vis[maxn*10];
ll B1,B2;
void add(ll x,ll v){
while(x<=B1){
tree[x]+=v; x+=x&-x;
}
}
ll ask(ll x){
ll v=0;
while(x){
v+=tree[x]; x-=x&-x;
} return v;
}
ll Id(ll x){
if(x<=sq) return id1[x];
return id2[n/x];
}
ll Get(ll x){
if(x<=B2) return sum[x];
if(x<=B1) return ask(x)+sum[B2];
return g[Id(x)];
}
int main(){
scanf("%lld",&n);
B1=pow(n/log2(n),0.66), B2=sqrt(n);
B1=max(B1,B2);
for(ll i=1;i<=n;i++){
ll d=n/i, r=n/d;
if(d<=B1) break;
w[++len]=d;
g[len]=d; i=r;
if(d<=sq) id1[d]=len;
else id2[n/d]=len;
i=r;
}
for(ll i=1;i<=B2;i++) sum[i]=i;
for(ll i=B2+1;i<=B1;i++) add(i,1);
for(ll i=2;i*i<=n;i++){
if(vis[i]) continue;
++cnt;
ll t=i*i; double inv=1.0/i;
for(ll j=1;j<=len&&w[j]>=t;j++)
g[j]-=Get(w[j]*inv+1e-6)-cnt;
for(ll j=B2;j>=t;j--) sum[j]-=sum[(int)(j*inv+1e-6)]-cnt;
for(ll j=i*i,o=max(B1,sq);j<=o;j+=i)
if(!vis[j]){
vis[j]=true;
if(j>B2&&j<=B1) add(j,-1);
}
}
printf("%lld",Get(n)-1);
return 0;
}