杜教筛小结
首先的首先,%%%%%%杜教
……咳咳,言归正传,现在来总结一下杜教筛……
杜教筛大概是一种用于快速处理积性函数前缀和的有力工具,主要利用了狄利克雷卷积的知识
我们用高中均值不等式的复杂度分析可以得到其复杂度是O(n23)的
具体的讲解我就不写了……留几篇比较好的博客。
https://www.cnblogs.com/abclzr/p/6242020.html
这里总结一下我做过的题目吧……
由于太水以至于没打……
可以当做板子练习题的题目。
深刻的记住了一个式子……之前一直没有这种意识,有一些转换的确很显然但是就是没想过
d∣ij⇔d(i,d)∣j
……自己打公式太麻烦了,放弃……
……其实怎么演都能演出来,关键就看第一步转化能不能出来
代码:

1 #include <cstdio> 2 #include <map> 3 #include <cstring> 4 using namespace std; 5 #define mod 1000000007 6 #define N 1000000 7 #define LL long long 8 int n,prime[N/10],tot,vis[N+10],mu[N+10]; 9 inline void intn() 10 { 11 register int i,j,tmp; 12 for(mu[1]=1,i=2;i<=N;++i) 13 { 14 if(!vis[i])prime[++tot]=i,mu[i]=-1; 15 for(j=1;j<=tot&&(tmp=i*prime[j])<=N;++j) 16 {vis[tmp]=1;if(i%prime[j]==0){mu[tmp]=0;break;}mu[tmp]=-mu[i];} 17 } 18 for(i=1;i<=N;++i)mu[i]+=mu[i-1]; 19 } 20 map<int,int> mmp; 21 inline int getmu(int x) 22 { 23 if(x<=N)return mu[x]; 24 if(mmp.count(x))return mmp[x]; 25 register int i,last,ret=1; 26 for(i=2;i<=x;i=last+1) 27 last=x/(x/i),ret=(ret-((LL)(last-i+1)*getmu(x/i)%mod))%mod; 28 return mmp[x]=ret; 29 } 30 inline int calc2(int x) 31 { 32 register int i,last,ret=0; 33 for(i=1;i<=x;i=last+1) 34 last=x/(x/i),ret=(ret+((LL)(last-i+1)*(x/i)%mod))%mod; 35 return (LL)ret*ret%mod; 36 } 37 int main() 38 { 39 // freopen("Ark.in","r",stdin); 40 register int i,last,ans=0; 41 scanf("%d",&n);intn(); 42 for(i=1;i<=n;i=last+1) 43 last=n/(n/i),ans=(ans+((LL)(getmu(last)-getmu(i-1))*calc2(n/i)%mod) )%mod; 44 printf("%d\n",(ans+mod)%mod); 45 }
然后这题我们发现n比较小,m却比较大
所以考虑与枚举n有关的算法……
设S(n,m)=∑ni=1φ(i∗n)
那么我们知道φ是个积性函数……所以上来质因数分解一发
设n=∏paii
那么有S(n,m)=pai−1i∗S(npai−1i,m)
然后我们把p都干下去了之后,
考虑φ(p∗q)=φ(p)∗φ(q),(p,q)==1
那么我们有S(n,m)=φ(pi)∗∑mi=1φ(npi∗i)∗[(i,pi)==1]+pi∗∑mi=1φ(n∗ipi)∗[pi|i]
又因为φ(p)==p−1
所以我们可以凑个整……
S(n,m)=φ(pi)∗∑mi=1φ(npi∗i)+∑⌊mpi⌋i=1φ(n∗i)
S(n,m)=S(npi,m)+S(n,⌊mpi⌋)
这样我们就可以记忆化搞了
边界条件n==1就是φ前缀和,杜教筛之。

1 #include <cstdio> 2 #include <cstring> 3 #include <map> 4 using namespace std; 5 #define N 1000000 6 #define LL long long 7 #define mod 1000000007 8 int prime[N/10],tot,mind[N+10],phi[N+10],sum[N+10]; 9 inline void intn() 10 { 11 register int i,j,tmp; 12 phi[1]=1; 13 for(i=2;i<=N;++i) 14 { 15 if(!mind[i])prime[++tot]=i,mind[i]=i,phi[i]=i-1; 16 for(j=1;j<=tot&&(tmp=i*prime[j])<=N;++j) 17 { 18 mind[tmp]=prime[j]; 19 if(i%prime[j]==0){phi[tmp]=phi[i]*prime[j];break;} 20 phi[tmp]=phi[i]*phi[prime[j]]; 21 } 22 } 23 for(i=1;i<=N;++i)sum[i]=(sum[i-1]+phi[i])%mod; 24 } 25 inline int quick_mod(int di,int mi) 26 { 27 int ans=1; 28 for(di%=mod;mi;mi>>=1,di=(LL)di*di%mod) 29 if(mi&1)ans=(LL)ans*di%mod; 30 return ans; 31 } 32 inline int divide(int x) 33 { 34 register int ret=1,i; 35 for(i=1;(LL)prime[i]*prime[i]<=x;++i) 36 if(x%prime[i]==0) 37 { 38 ret*=prime[i]; 39 while(x%prime[i]==0)x/=prime[i]; 40 } 41 return ret*x; 42 } 43 #define N1 100010 44 map<int,int>mmp; 45 int mem[N1]; 46 inline int getphi(int x) 47 { 48 if(x<=N)return sum[x]; 49 else if(mmp.count(x))return mmp[x]; 50 int ret=((LL)x*(x+1)/2%mod); 51 register int i,last; 52 for(i=2;i<=x;i=last+1) 53 last=x/(x/i),ret=( ret+mod-( (LL)(last-i+1)*getphi(x/i)%mod ) )%mod; 54 return mmp[x]=ret; 55 } 56 inline int calc(int a,int b) 57 { 58 if(!b)return 0; 59 if(b==1)return phi[a]; 60 if(a==1)return getphi(b); 61 return ((LL)calc(a/mind[a],b)*(mind[a]-1)%mod+calc(a,b/mind[a]))%mod; 62 } 63 int main() 64 { 65 // freopen("Ark.in","r",stdin); 66 register int n,m,ans=0,tmp,x,i; 67 scanf("%d%d",&n,&m),intn(); 68 memset(mem,-1,sizeof(mem)); 69 for(i=1;i<=n;++i) 70 { 71 x=divide(i),tmp=i/x; 72 if(mem[x]==-1)mem[x]=calc(x,m); 73 ans=(ans+(LL)tmp*mem[x]%mod)%mod; 74 } 75 printf("%d\n",ans); 76 }
首先你发现∑μ(i2)是出来搞笑的,输出1即可
然后我们考虑一下这个∑ni=1φ(i2)
由于φ(i)是积性函数,所以我们考虑
φ(i)=∏φ(pi)∗pai−1i
φ(i2)=∏φ(pi)∗pai−1+aii
φ(i2)=∏φ(pi)∗pai−1i∗paii
所以……φ(i2)=i∗φ(i)
设f(i)=φ(i2)
那么我们不是知道∑d|nφ(d)=n 嘛
那么转化一下 ∑d|nd∗φ(d)∗nd=n∗∑d|nφ(d)=n2
所以我们有积性函数f(i)=iφ(i),g(i)=i,h(i)=i2
这样就可以杜教筛了,设大写字母为前缀和,过程略……
最后结果是F(i)=H(i)−∑mj=2g(j)∗F(⌊mj⌋)
其中,G(i)=n∗(n+1)2,H(i)=n∗(n+1)∗(2n+1)6
代码:

1 #include <cstdio> 2 #include <map> 3 #include <cstring> 4 using namespace std; 5 #define N 1000000 6 #define mod 1000000007 7 int n,inv6,vis[N+10],prime[N/10],tot,phi[N+10],sum[N+10]; 8 map<int,int> mmp; 9 #define LL long long 10 inline void intn() 11 { 12 register int i,j,tmp; 13 for(phi[1]=1,i=2;i<=N;++i) 14 { 15 if(!vis[i])prime[++tot]=i,phi[i]=i-1; 16 for(j=1;j<=tot&&(tmp=i*prime[j])<=N;++j) 17 { 18 if(i%prime[j]==0) 19 {vis[tmp]=1,phi[tmp]=phi[i]*prime[j];break;} 20 vis[tmp]=1,phi[tmp]=phi[i]*phi[prime[j]]; 21 } 22 } 23 for(i=1;i<=N;++i) 24 sum[i]=(sum[i-1]+(LL)i*phi[i]%mod)%mod; 25 } 26 inline int quick_mod(int di,int mi) 27 { 28 int ans=1; 29 for(;mi;mi>>=1,di=(LL)di*di%mod) 30 if(mi&1)ans=(LL)ans*di%mod; 31 return ans; 32 } 33 inline int getf(int x) 34 { 35 if(x<=N)return sum[x]; 36 if(mmp.count(x))return mmp[x]; 37 register int i,last,ret=(LL)x*(x+1)%mod*(2*x+1)%mod*inv6%mod; 38 for(i=2;i<=x;i=last+1) 39 last=x/(x/i), 40 ret=( ret +mod -(LL)getf(x/i)*( (LL)(last-i+1)*(last+i)/2 %mod )%mod )%mod; 41 return mmp[x]=ret; 42 } 43 int main() 44 { 45 // freopen("Ark.in","r",stdin); 46 inv6=quick_mod(6,mod-2); 47 scanf("%d",&n),intn(); 48 printf("1\n%d\n",getf(n)); 49 }
我们先反演一波……
定义f(i)为gcd(a1,a2,......,an)==i的方案数,F(i)为i|gcd(a1,a2,......,an)的方案数
那么有F(i)=∑i|nf(n)
然后我们演他一下,可以得到:
f(n)=∑n|dμ(dn)∗F(d)
我们考虑一下……F(d)=(⌊rd⌋−⌊l−1d⌋)n
然后我们考虑枚举i=dn
f(n)=∑⌊rn⌋i=1μ(i)∗(⌊ri∗n⌋−⌊l−1i∗n⌋)n
然后我们对⌊ri∗n⌋和⌊l−1i∗n⌋除法分块
再杜教筛一个μ的前缀和就行了。
代码:

1 #include <cstdio> 2 #include <map> 3 #include <cstring> 4 using namespace std; 5 #define mod 1000000007 6 #define LL long long 7 #define inf 0x3f3f3f3f 8 #define N 1000000 9 int prime[N],tot,mu[N+10],vis[N+10]; 10 int n,d,l,r; 11 inline void intn() 12 { 13 register int i,j,tmp; 14 for(mu[1]=1,i=2;i<=N;++i) 15 { 16 if(!vis[i])prime[++tot]=i,mu[i]=-1; 17 for(j=1;j<=tot&&(tmp=i*prime[j])<=N;++j) 18 { 19 vis[tmp]=1; 20 if(i%prime[j]==0)break; 21 mu[tmp]=-mu[i]; 22 } 23 } 24 for(i=2;i<=N;++i)mu[i]+=mu[i-1]; 25 } 26 inline int min(int a,int b){return a<b?a:b;} 27 map<int,int> mmp; 28 inline LL getmu(int x) 29 { 30 if(x<=N)return mu[x]; 31 if(mmp.count(x))return mmp[x]; 32 LL ret=1; 33 for(register int i=2,last;i<=x;i=last+1) 34 last=x/(x/i),ret=( ret-getmu(x/i)*(LL)(last-i+1)%mod )%mod; 35 return mmp[x]=ret; 36 } 37 inline LL quick_mod(LL di,int mi) 38 { 39 LL ans=1; 40 for(;mi;mi>>=1,di=di*di%mod) 41 if(mi&1)ans=ans*di%mod; 42 return ans; 43 } 44 signed main() 45 { 46 register int i,last; 47 scanf("%d%d%d%d",&n,&d,&l,&r); 48 r=r/d,l=(l-1)/d,intn(); 49 LL ans=0; 50 for(i=1;i<=r;i=last+1) 51 { 52 last= (l/i) ? min( r/(r/i), l/(l/i) ) : r/(r/i) , 53 ans=(ans+ ( getmu(last)-getmu(i-1) )*quick_mod((r/i-l/i),n) )%mod; 54 } 55 printf("%lld\n",(ans%mod+mod)%mod); 56 }
…………不得不说是个好题
被拿去做考试题了
结果只打了24分,而不是常见的84分暴力……原因待会再说
介绍一下考试历程
首先我观察了一下十进制下都以谁为分母是纯循环的……
发现2,5,10都不是
然后发现1无论何时是纯循环的
然后发现这是个完全积性函数!!!哇太棒了!
于是我就把这个函数命名为f(i),当1i纯循环时f(i)为1,否则为0
然后我就开始反演……
∑ni=1∑mj=1f(j)[(i,j)==1]
∑ni=1∑mj=1f(j)∑d|(i,j)μ(d)
然后我们枚举d得到
∑nd=1μ(d)⌊nd⌋∑⌊md⌋j=1f(j∗d)
由于f(i)是完全积性函数,所以f(j∗d)=f(j)∗f(d)
所以可以写成∑nd=1μ(d)f(d)⌊nd⌋∑⌊md⌋j=1f(j)
然后我成功的yy出了怎么线性筛f(i)的值
......然后不会处理∑⌊md⌋j=1f(j)这一部分了……我以为可以杜教筛这部分,结果发现不行
最后在空间允许的范围内打了时间复杂度为O(n)的24分算法
考完试和wq交流之后我发现......
f(i)⇔[(i,k)==1]
……当时整个人傻了
后来的后来发现自己式子的某个主流题解是一样的……
更加心里苦
我们还是来证明一下上面那个式子为啥成立吧……
设循环节长度为L
那么根据题给的循环节生成方式,应该有x%y=x∗kL%y
由于题面要求被计算的x和y互质,所以我们可以消去x
从而有kL≡1(mody)
这就意味着k在模y的意义下有逆元
如果一个数在模意义下有逆元……我们回想一下exgcd的过程
那么就会有(k,y)==1
这就好说了……真是心累
然后还有一点我考试没有想到的,就是快速计算F(n)=∑ni=1[(i,k)==1]
由于我们知道(a,b)==(a−k∗b,b)
所以∑ki=1[(i,k)==1]==∑2∗ki=k+1[(i,k)==1]
由此我们可以归纳出
F(n)=⌊nk⌋F(k)+F(n%k)
我们回到刚才我演的那个式子
∑nd=1μ(d)[(k,d)==1]⌊nd⌋F(⌊md⌋)
这样,我们预处理1~k内的F值,就可以O(n)回答询问了,可以得到84分
然而事实证明剩下的16分分值和难度不成正比……
我们考虑上面这个东西,⌊nd⌋和⌊md⌋是可以除法分块的
那么我们如果能处理出来一个∑ni=1μ(i)f(i)前缀和就能O(√n)回答了……
这里面的f(i)是我上面定义的f(i)⇔[(i,k)==1]
我们考虑怎么处理这个前缀和……
设其为G(n,k),考虑化简为子问题递归解决
对于k的某一个质因子p,我们可以把k写成k=paq((p,q)==1)的形式
那么如果(i,k)==1,则(i,q)==1且(i,p)==1
如果我们处理出G(n,q),再减去与q互质但是与p不互质的数的个数,就可以得到G(n,k)了
我们假设某个与q互质但是与p不互质的数i可以表示为i=pcy((y,q)==1,(y,p)==1)
由于与p不互质,所以c>0又因为c>=2时μ(i)==0所以c==1
因此我们考虑的是一系列形如i=py的i
我们写一下这个式子……
G(n,k)=G(n,q)−∑⌊np⌋y=1μ(py)[(y,q)==1][(y,p)==1]
因为(p,y)==1,μ(py)=μ(p)μ(y)
因此就会有G(n,k)=G(n,q)−∑⌊np⌋y=1μ(p)μ(y)[(y,p)==1][(y,q)==1]
我们把μ(p)=−1带入,有G(n,k)=G(n,q)+∑⌊np⌋y=1μ(y)[(y,p)==1][(y,q)==1]
又由于我们一开始利用的条件,“如果(i,k)==1,则(i,q)==1且(i,p)==1”
所以[(y,p)==1][(y,q)==1]⇔[(y,k)==1]
所以原式可以写成G(n,k)=G(n,q)+∑⌊np⌋y=1μ(y)[(y,k)==1]
即G(n,k)=G(n,q)+G(⌊np⌋,k)
这样我们就可以记忆化搜索我们的G值,边界条件是n==0时G(0,k)=0,n==1时G(1,k)=1;
k==1时G(n,1)=∑ni=1μ(i),杜教筛之。
这个部分和上面的bzoj3309一样,都是对递归的巧妙应用
这种定义一个函数求递推式来帮助思考的方法很有效!
这样这题就被解决了!的确是优美的题目啊!
不知道为什么跑的贼慢的代码:

1 #include <map> 2 #include <cstdio> 3 #include <cstring> 4 using namespace std; 5 #define N 20000000 6 #define K 2010 7 #define LL long long 8 int n,m,k; 9 int prime[N/10+10],tot,mu[N+10],sum[K]; 10 bool vis[N+10]; 11 inline int min(int a,int b){return a<b?a:b;} 12 inline int gcd(int a,int b){return b==0?a:gcd(b,a%b);} 13 inline void init() 14 { 15 register int i,j,tmp; 16 for(i=1;i<=k;++i)sum[i]=sum[i-1]+(gcd(i,k)==1); 17 for(mu[1]=1,i=2;i<=N;++i) 18 { 19 if(!vis[i])prime[++tot]=i,mu[i]=-1; 20 for(j=1;j<=tot&&(tmp=i*prime[j])<=N;++j) 21 { 22 vis[tmp]=1; 23 if(i%prime[j]==0){mu[tmp]=0;break;} 24 mu[tmp]=-mu[i]; 25 } 26 } 27 for(i=1;i<=N;++i)mu[i]+=mu[i-1]; 28 } 29 int bin[100],ge; 30 inline void divide(int kkk) 31 { 32 register int i; 33 for(i=1;prime[i]*prime[i]<=kkk;++i) 34 if(kkk%prime[i]==0) 35 { 36 bin[++ge]=prime[i]; 37 while(kkk%prime[i]==0)kkk/=prime[i]; 38 } 39 if(kkk>1)bin[++ge]=kkk; 40 } 41 inline int getF(int x) 42 { 43 if(x<=k)return sum[x]; 44 return (x/k)*sum[k]+sum[x%k]; 45 } 46 map<int,int>smu; 47 inline int getmu(int x) 48 { 49 if(x<=N)return mu[x]; 50 if(smu.count(x))return smu[x]; 51 register int i,last,ret=1; 52 for(i=2;i<=x;i=last+1) 53 last=x/(x/i),ret-=(LL)getmu(x/i)*(last-i+1); 54 return smu[x]=ret; 55 } 56 map<int,int>mmp[7]; 57 inline int getsum(int x,int layer) 58 { 59 if(x<=1)return x; 60 if(layer==0)return getmu(x); 61 if(mmp[layer].count(x))return mmp[layer][x]; 62 return mmp[layer][x]=getsum(x,layer-1)+getsum(x/bin[layer],layer); 63 } 64 int main() 65 { 66 scanf("%d%d%d",&n,&m,&k); 67 init(),divide(k); 68 register int i,to=min(n,m),last; 69 LL ans=0; 70 // if(n>m)n^=m,m^=n,n^=m; 71 for(i=1;i<=to;i=last+1) 72 last=min(n/(n/i),m/(m/i)), 73 ans+=(getsum(last,ge)-getsum(i-1,ge))*(LL)getF(m/i)*(n/i); 74 printf("%lld\n",ans); 75 }
大概我最近做的题就这些……虽然还有另外一道模拟赛的题,式子还不错,先不放出来了……
杜教筛……怎么说呢,算是对狄利克雷卷积的灵活应用吧
作为优化的工具,不管是复杂度还是思想都很优秀
逐渐有一点点数学题的手感了知道上来无脑反演了,加油咯……
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· 一次Java后端服务间歇性响应慢的问题排查记录
· dotnet 源代码生成器分析器入门
· ASP.NET Core 模型验证消息的本地化新姿势
· 对象命名为何需要避免'-er'和'-or'后缀
· “你见过凌晨四点的洛杉矶吗?”--《我们为什么要睡觉》
· 编程神器Trae:当我用上后,才知道自己的创造力被低估了多少
· C# 从零开始使用Layui.Wpf库开发WPF客户端
· 开发的设计和重构,为开发效率服务
· 从零开始开发一个 MCP Server!