Description
求 S(n)=∑i=1nf(i)
Solution
part1(核心公式推导):
对于任何函数 g(i),都有:
∑i=1n(f∗g)(i)
=∑i=1n∑d∣if(d)g(di)
=∑i=1ng(i)∑i∗j≤nf(j)
=∑i=1ng(i)S(⌊in⌋)
那么 g(1)S(n)=∑i=1n(f∗g)(i)−∑i=2ng(i)S(⌊in⌋)
杜教筛就要构造一个 g(i),使得 ∑i=1n(f∗g)(i)与 g(i)能快速计算
part2(复杂度证明):
假设计算 S(n)的复杂度为 T(n)
那么 T(n)=O(n)+∑i=1nT(i)+T(in)( i从 1枚举到 n时所有不同的 T(⌊in⌋)之和)
据 tangjz大佬所说:这里只展开一层就可以了,更深层的复杂度是高阶小量
所以 T(n)=∑i=1nO(i)+O(in)
=∑i=1nO(in)
=n∫1nx−21dx
=n⋅2x21∣1n
=O(n43)
如果预处理了前 k(k≥n)个数的前缀和,那么
T(n)=O(k)+∑i=1knO(in)=O(k+kn)
当 k=O(n32)时 T(n)=O(n32)
part3(example):
就拿 φ(i)举例吧,有一个公式 φ∗1=id1
所以 1(1)S(n)=∑i=1nid1(i)−∑i=2n1(i)S(⌊in⌋)
S(n)=2n(n+1)−∑i=2nS(⌊in⌋)
part4(代码实现中的注意点):
1.因为数据不止 1组,所以如果预处理 k个数的话,复杂度为 O(k+Tkn)
根据基本不等式, k取 (Tn)32最为合适
2.计算 2n(n+1)时,因为有 n+1,所以可能会爆 int,数论分块中的 l和 r也是如此
3.关于记忆化,有两种方法。一种是开unordered_map,另外一种是假设现在正在计算 S(n′),那么只要判断 p[⌊n′n⌋]即可,数组大小可开到 O(kn)的规模
Code
#include<cstdio>
#include<cstring>
typedef unsigned long long ll;
typedef unsigned int ui;
const int N=5000000;
int T,i,pr[N/14],n,cnt,j,t,mu[N],m[1291],nn;
bool vis[N];
ll phi[N],p[1291];
ll calp(int n){
if (n<N) return phi[n];
if (p[nn/n]) return p[nn/n];
ll ans=(1ll*n+1)*n>>1;
for (ui l=2,r;l<=n;l=r+1) r=n/(n/l),ans-=calp(n/l)*(r-l+1);
return p[nn/n]=ans;
}
int calm(int n){
if (n<N) return mu[n];
if (m[nn/n]) return m[nn/n];
int ans=1;
for (ui l=2,r;l<=n;l=r+1) r=n/(n/l),ans-=calm(n/l)*(r-l+1);
return m[nn/n]=ans;
}
int main(){
scanf("%d",&T);
phi[1]=mu[1]=1;
for (i=2;i<N;i++){
if (!vis[i]) pr[cnt++]=i,phi[i]=i-1,mu[i]=-1;
for (j=0;j<cnt && (t=i*pr[j])<N;j++){
vis[t]=1;
if (!(i%pr[j])){
phi[t]=phi[i]*pr[j];
mu[t]=0;
break;
}
phi[t]=phi[i]*(pr[j]-1);
mu[t]=-mu[i];
}
}
for (i=2;i<N;i++) phi[i]+=phi[i-1],mu[i]+=mu[i-1];
for (;T--;) scanf("%d",&n),nn=n,memset(p,0,sizeof(p)),
memset(m,0,sizeof(m)),printf("%llu %d\n",calp(n),calm(n));
}