先写一下(Dirichlet) 前缀和
就是求 (b_i=sumlimits_{j|i} a_j)
(n le 2 imes 10^6)
发现本质上 (a_i) 对 (b_j) 有贡献是在唯一分解后 (a:c_kle b:c_k)
可以筛完因数之后这样做:
For(i,1,cnt)
{
for(reg int j=1;j*pri[i]*1ll<=n;++j) a[j*pri[i]]+=a[j];
}
具体的理解就是每次加的时候都是加上的这个因数上的前缀和
(这种思想感觉挺妙的,不过感觉挺套路……)
然后说这题的做法,一开始我把约数函数一拆,然后当场自闭,三张草稿纸瞬间交代
保留着约数函数然后按照 简单的数学题 那个的做法可以得到:
[sum_{T=1}^nsum_{g|T} d(g)mu(frac T g) sum_{i=1}^{lfloorfrac n T
floor} sum_{j=1}^{lfloorfrac n T
floor} d(iT)d(jT)
]
令 (f=d * mu)
(f(T)=sum_{g|T} f(g)mu(frac T g))
其实可以用相关的组合式子给出 (f(x)=1) (直接唯一分解出来之后列式子就行了)
然后要求的就是
[sum_{T=1}^nsum_{i=1}^{lfloorfrac n T
floor} sum_{j=1}^{lfloorfrac n T
floor} d(iT)d(jT)
]
(d) 直接筛,后面的部分用上面说的 (Dirichlet) 前缀和的转化(后缀和)
时间复杂度降到了 (O(nlog log n)) (这里认为 (n,m) 同阶)
#include<bits/stdc++.h>
using namespace std;
#define reg register
#define For(i,a,b) for(reg int i=a;i<=b;++i)
#define Down(i,a,b) for(reg int i=a;i>=b;--i)
namespace yspm{
inline int read()
{
int res=0,f=1; char k;
while(!isdigit(k=getchar())) if(k=='-') f=-1;
while(isdigit(k)) res=res*10+k-'0',k=getchar();
return res*f;
}
const int N=5e7+10;
int l[N],mod,pri[N/10],n,ans,d[N],cnt;
bool fl[N];
inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int del(int x,int y){return x-y<0?x-y+mod:x-y;}
inline int mul(int x,int y){return 1ll*x*y-1ll*x*y/mod*mod;}
signed main()
{
freopen("sum.in","r",stdin);
freopen("sum.out","w",stdout);
n=read(); mod=read();
d[1]=1;
for(reg int i=2;i<=n;++i)
{
if(!fl[i]) pri[++cnt]=i,d[i]=2,l[i]=1;
for(reg int j=1;j<=cnt&&1ll*pri[j]*i<=n;++j)
{
fl[i*pri[j]]=1;
if(i%pri[j]==0)
{
d[pri[j]*i]=d[i]/(l[i]+1)*(l[i]+2);
l[i*pri[j]]=l[i]+1;
break;
}else d[pri[j]*i]=d[i]*2,l[i*pri[j]]=1;
}
}
for(reg int i=1;i<=cnt;++i) for(reg int j=n/pri[i];j>=1;--j) d[j]=add(d[j],d[j*pri[i]]);
For(i,1,n) ans=add(ans,mul(d[i],d[i]));
printf("%d
",ans);
return 0;
}
}
signed main(){return yspm::main();}