PN筛入门
PN 筛
Powerful Number 筛
oi-wiki 上说是杜教筛的 expand,但是这玩意 只能做积性函数前缀和
若未加说明,以下涉及的
Powerful number
定义一个数
性质:
-
证明容易,对于每个
,加入 ,否则将 加入 ,余下 加入 即可 -
Proof:
-
的 每个质因子都不大于
算法内容
考虑求出
我们考虑构造一个积性函数
满足以下要求:
易求前缀和
再构造
函数
-
,这是因为 -
。根据 为积性函数可知。 -
这告诉我们:
而且只需求出所有
现在我们设
则有:
这告诉我们,若可以快速求得
整个PN的流程可以梳理为:
-
设出
必须容易求出(快速计算) -
逆设出
,求出所有 -
求
-
求
-
数论分块求出
其中第二步、第五步是
如果可以直接运用公式法得出最好,如果不行,第三步可以考虑杜教筛,第四步考虑根据定义式计算。
考虑第四步的复杂度:
至于求出所有
甚至说如果单次询问不需要记忆化所有
杜教筛与 PN 筛的联系在于求
,事实上如果可以构造出杜教筛所要求的 ,就可以直接利用杜教筛求解而不需要 筛。 但若构造不出来,
是一个简化过程,也许 会更加容易构造出杜教筛所需。 况且一个很优秀的地方是这里面用到的
都是 。这意味着杜教筛的总复杂度不变。
其复杂度就是
例题:模版-Min_25筛
定义积性函数
显然构造
现在考虑求出
求解
看到
可以想到设计 。 所以
。 带入杜教筛的式子,有:
问题至此解决,下附一份板子。
#include<bits/stdc++.h>
using namespace std;
#define N 1050500
#define int long long
const int it=1e6+7,p=1e9+7;
int v[N],pri[N],tot,phi[N],n,m,s[N],inv2,inv6,inv4,g[N];
int power(int a,int b){
int ans=1;
while(b){
if(b&1)ans=ans*a%p;
a=a*a%p;b>>=1;
}
return ans;
}
unordered_map<int,int>sit,h;
void init(){
phi[1]=1;
for(int i=2;i<it;i++){
if(!v[i]){
pri[++tot]=i;phi[i]=i-1;
}
for(int j=1;j<=tot&&i*pri[j]<it;++j){
v[pri[j]*i]=1;
if(i%pri[j]==0){
phi[pri[j]*i]=pri[j]*phi[i];break;
}
phi[pri[j]*i]=phi[pri[j]]*phi[i];
}
}
for(int i=1;i<it;++i)s[i]=(s[i-1]+i%p*phi[i]%p)%p,g[i]=i*phi[i]%p;
}
int ych(int n){
n%=p;
return n*(n+1)%p*inv2%p;
}
int lfh(int n){
n%=p;
return n*n%p*(n+1)%p*(n+1)%p*inv4%p;
}
int pfh(int n){
n%=p;
return n*(n+1)%p*(n+n+1)%p*inv6%p;
}
int calc(int n){
if(n<it)return s[n];
if(sit[n]!=0)return sit[n];
// cout<<"calc: "<<n<<"\n";
int res=pfh(n);int lst=1,cur=0;
for(int l=2,r;l<=n;l=r+1){
r=min(n,n/(n/l));cur=ych(r);
res=(res+p-(cur-lst)%p*calc(n/l)%p)%p;lst=cur;
}res=(res%p+p)%p;
return sit[n]=res;
}
//以上是杜教筛求 G
int res=0;
void sol(int now,int val,int pos){//现在以及算到PN:now,处理到第pos个质数,目前h值为val
res+=val*calc(n/now)%p;res%=p;
// cout<<now<<' '<<val<<" "<<pos<<"\n";
//必须写除法,否则越界死掉
for(int i=pos;i<=tot;i++){
if(now>n/pri[i]/pri[i])return ;
int x=now*pri[i],mul=pri[i];
for(int j=2;j;++j){
if(x>n/pri[i])break;
mul*=pri[i];x*=pri[i];
if(!h[mul]){
int w=mul%p*(mul%p-1)%p;//f(n)
int nowmul=1;
for(int c=0;c<j;++c){
if(c!=1){
int t=mul/nowmul;
w=(w+p-h[nowmul]*((t-t/pri[i])%p)%p*(t%p)%p)%p;
}
nowmul=nowmul*pri[i];
}
h[mul]=w;
}
sol(now*mul,val*h[mul]%p,i+1);
}
}
}
signed main(){
cin>>n;inv2=power(2,p-2);inv4=power(4,p-2);inv6=power(6,p-2);
init();h[1]=1;
sol(1,1,1);
cout<<(res%p+p)%p<<"\n";
}
简单的函数
这告诉我们求G也可以综合利用已知信息
给定函数:
求
考虑PN筛构造。
注意到
所以可以构造
对于一个积性函数,强行钦定含某个质因子的数多一个带权系数,仍然是积性函数
因此
现在考虑求解
求解
考虑
所以可以递推求解
而且因为求解
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int it=3e6+7,it2=it>>1;
const int p=1e9+7,inv2=(p+1)>>1;
#define N 3050500
int pi[N],phi[N],n,m,v[N],tot,sph[N],sph2[N];
unordered_map<int,int>s1,s2;
void init(){
phi[1]=1;
for(int i=2;i<it;i++){
if(!v[i]){
pi[++tot]=i;phi[i]=i-1;
}
for(int j=1;j<=tot&&i*pi[j]<it;++j){
v[pi[j]*i]=1;
if(i%pi[j]==0){
phi[pi[j]*i]=pi[j]*phi[i];break;
}
phi[pi[j]*i]=phi[pi[j]]*phi[i];
}
}
for(int i=1;i<it;i++)sph[i]=(sph[i-1]+phi[i])%p;
for(int i=1;i<it2;i++)sph2[i]=(sph2[i-1]+phi[i<<1])%p;
// for(int i=1;i<=20;i++)cout<<sph2[i]<<" ";cout<<"\n";
}
int pfh(int x){
x%=p;
return x*(x+1)%p*inv2%p;
}
int calcS1(int x){
if(x<it)return sph[x];
if(s1[x]!=0)return s1[x];
int res=pfh(x);
// cout<<"! "<<res<<"\n";
for(int l=2,r;l<=x;l=r+1){
r=min(x,x/(x/l));
res-=(r-l+1)%p*calcS1(x/l)%p;
// cout<<(r-l+1)<<" "<<calcS1(x/l)<<"\n";
}
res=(res%p+p)%p;
return s1[x]=res%p;
}
int calcS2(int x){
if(x<it2)return sph2[x];
if(s2[x]!=0)return s2[x];
return s2[x]=(calcS1(x)+calcS2(x>>1))%p;
}
int calcG(int x){
// cout<<"Calc G "<<x<<" "<<calcS1(x)<<" "<<calcS2(x>>1)<<"\n";
return (calcS1(x)+2*calcS2(x>>1));
}
int h[105050][40],ph[105050][40];
int res=0;
void dfs(int now,int val,int pos){
// cout<<now<<" "<<val<<" "<<calcG(n/now)<<"\n";
res+=val*calcG(n/now)%p;res%=p;
for(int i=pos;i<=tot;++i){
int x=pi[i],w=now;
if(now>n/(x*x))return ;
w*=x;
for(int j=2;j;++j){
if(w>n/x)break;
w*=x;
dfs(w,val*h[i][j],i+1);
}
}
}
signed main(){
// ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>n;
init();
// for(int i=1;i<=20;i++)cout<<calcS2(i)<<" ";
// cout<<calcS1(6)<<"\n";
for(int i=1;i<=tot;i++){
h[i][0]=1;int x=pi[i],now=1;ph[i][0]=1;
if(x*x>n)break;
for(int j=1;j;++j){
if(now>n/x)break;
// cout<<now<< " "<<x<<" !\n";
int lst=now;
now*=x;ph[i][j]=now-lst;
if(j==1)continue;
h[i][j]=pi[i]^j;
// cout<<" "<<ph[i][j]<<" "<<h[i][j]<<"\n";
for(int k=0;k<j;++k){
int mul=ph[i][j-k];
if(i==1)mul*=3,mul%=p;
// cout<<" "<<x<<" "<<mul<<"\n";
h[i][j]-=h[i][k]*mul;
}
// cout<<" "<<h[i][j]<<"\n";
}
}
// cout<<"output\n";
dfs(1,1,1);res=(res%p+p)%p;
cout<<res<<"\n";
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示