Processing math: 100%

杜教筛小结

首先的首先,%%%%%%杜教

……咳咳,言归正传,现在来总结一下杜教筛……

杜教筛大概是一种用于快速处理积性函数前缀和的有力工具,主要利用了狄利克雷卷积的知识

我们用高中均值不等式的复杂度分析可以得到其复杂度是O(n23)

具体的讲解我就不写了……留几篇比较好的博客。

https://www.cnblogs.com/abclzr/p/6242020.html

这里总结一下我做过的题目吧……

1.bzoj3944

由于太水以至于没打……

可以当做板子练习题的题目。

2.bzoj4176

深刻的记住了一个式子……之前一直没有这种意识,有一些转换的确很显然但是就是没想过

dijd(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 }
bzoj4176
复制代码

3.bzoj3512

然后这题我们发现n比较小,m却比较大

所以考虑与枚举n有关的算法……

S(n,m)=ni=1φ(in)

那么我们知道φ是个积性函数……所以上来质因数分解一发

n=paii

那么有S(n,m)=pai1iS(npai1i,m)

然后我们把p都干下去了之后,

考虑φ(pq)=φ(p)φ(q),(p,q)==1

那么我们有S(n,m)=φ(pi)mi=1φ(npii)[(i,pi)==1]+pimi=1φ(nipi)[pi|i]

又因为φ(p)==p1

所以我们可以凑个整……

S(n,m)=φ(pi)mi=1φ(npii)+mpii=1φ(ni)

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 }
bzoj3512
复制代码

4.bzoj4916

 首先你发现μ(i2)是出来搞笑的,输出1即可

然后我们考虑一下这个ni=1φ(i2)

由于φ(i)是积性函数,所以我们考虑

φ(i)=φ(pi)pai1i

φ(i2)=φ(pi)pai1+aii

φ(i2)=φ(pi)pai1ipaii

所以……φ(i2)=iφ(i)

f(i)=φ(i2)

那么我们不是知道d|nφ(d)=n

那么转化一下 d|ndφ(d)nd=nd|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 }
bzoj4916
复制代码

5.bzoj3930

我们先反演一波……

定义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)=(rdl1d)n

然后我们考虑枚举i=dn

f(n)=rni=1μ(i)(rinl1in)n

然后我们对rinl1in除法分块

再杜教筛一个μ的前缀和就行了。

代码:

复制代码
 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 }
bzoj3930
复制代码

6.bzoj4652

…………不得不说是个好题

被拿去做考试题了

结果只打了24分,而不是常见的84分暴力……原因待会再说

介绍一下考试历程

首先我观察了一下十进制下都以谁为分母是纯循环的……

发现2,5,10都不是

然后发现1无论何时是纯循环的

然后发现这是个完全积性函数!!!哇太棒了!

于是我就把这个函数命名为f(i),当1i纯循环时f(i)为1,否则为0

然后我就开始反演……

ni=1mj=1f(j)[(i,j)==1]

ni=1mj=1f(j)d|(i,j)μ(d)

然后我们枚举d得到

nd=1μ(d)ndmdj=1f(jd)

由于f(i)是完全积性函数,所以f(jd)=f(j)f(d)

所以可以写成nd=1μ(d)f(d)ndmdj=1f(j)

然后我成功的yy出了怎么线性筛f(i)的值

......然后不会处理mdj=1f(j)这一部分了……我以为可以杜教筛这部分,结果发现不行

最后在空间允许的范围内打了时间复杂度为O(n)的24分算法

考完试和wq交流之后我发现......

f(i)[(i,k)==1]

……当时整个人傻了

后来的后来发现自己式子的某个主流题解是一样的……

更加心里苦

我们还是来证明一下上面那个式子为啥成立吧……

设循环节长度为L

那么根据题给的循环节生成方式,应该有x%y=xkL%y

由于题面要求被计算的xy互质,所以我们可以消去x

从而有kL1(mody)

这就意味着k在模y的意义下有逆元

如果一个数在模意义下有逆元……我们回想一下exgcd的过程

那么就会有(k,y)==1

这就好说了……真是心累

然后还有一点我考试没有想到的,就是快速计算F(n)=ni=1[(i,k)==1]

由于我们知道(a,b)==(akb,b)

所以ki=1[(i,k)==1]==2ki=k+1[(i,k)==1]

由此我们可以归纳出

F(n)=nkF(k)+F(n%k)

我们回到刚才我演的那个式子

nd=1μ(d)[(k,d)==1]ndF(md)

这样,我们预处理1~k内的F值,就可以O(n)回答询问了,可以得到84分

然而事实证明剩下的16分分值和难度不成正比……

我们考虑上面这个东西,ndmd是可以除法分块的

那么我们如果能处理出来一个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=pyi

我们写一下这个式子……

G(n,k)=G(n,q)npy=1μ(py)[(y,q)==1][(y,p)==1]

因为(p,y)==1,μ(py)=μ(p)μ(y)

因此就会有G(n,k)=G(n,q)npy=1μ(p)μ(y)[(y,p)==1][(y,q)==1]

我们把μ(p)=1带入,有G(n,k)=G(n,q)+npy=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)+npy=1μ(y)[(y,k)==1]

G(n,k)=G(n,q)+G(np,k)

这样我们就可以记忆化搜索我们的G值,边界条件是n==0G(0,k)=0,n==1G(1,k)=1

k==1G(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 }
bzoj4652
复制代码

大概我最近做的题就这些……虽然还有另外一道模拟赛的题,式子还不错,先不放出来了……

杜教筛……怎么说呢,算是对狄利克雷卷积的灵活应用吧

作为优化的工具,不管是复杂度还是思想都很优秀

逐渐有一点点数学题的手感了知道上来无脑反演了,加油咯……

posted @   LadyLex  阅读(536)  评论(0编辑  收藏  举报
编辑推荐:
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· 一次Java后端服务间歇性响应慢的问题排查记录
· dotnet 源代码生成器分析器入门
· ASP.NET Core 模型验证消息的本地化新姿势
· 对象命名为何需要避免'-er'和'-or'后缀
阅读排行:
· “你见过凌晨四点的洛杉矶吗?”--《我们为什么要睡觉》
· 编程神器Trae:当我用上后,才知道自己的创造力被低估了多少
· C# 从零开始使用Layui.Wpf库开发WPF客户端
· 开发的设计和重构,为开发效率服务
· 从零开始开发一个 MCP Server!
点击右上角即可分享
微信分享提示