学习笔记第三十六节:杜教筛

本文深入探讨了数论函数的求和技巧,包括整除分块递归、记忆化搜索以及通过构造方法处理完全积性函数求和等问题。文章详细讲解了如何优化递归算法的时间复杂度,以及如何通过线性筛预处理提高效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

正题

      当我们要求一个这样的东西的时候,我们就会很困惑。

      \sum_{i=1}^n \mu(i)或者\sum_{i=1}^n \varphi(i)

      记F(n)=\sum_{i=1}^n \mu(i)

      因为我们知道\mu*1=e,也就是\sum_{d\mid n}\mu(d) =[n=1]。(这条式子的证明看数论函数的介绍

      所以可以得到两条式子。

\sum_{k=1}^n (\mu*1)(k)=\sum_{i=1}^n1(i)\sum_{j=1}^{\lfloor\frac n i \rfloor}\mu(j)=\sum_{i=1}^n F(\lfloor \frac n i \rfloor)      

,至于第一个等号,解释是,对于一个i要产生贡献,必定是与那些相乘小于等于n的j,所以产生的总贡献就是这样子的,然后第二个等号就很显然了,直接替换F。

      \sum_{k=1}^n (\mu*1)(k)=\sum_{k=1}^ne(k)=1

      所以\sum_{i=1}^n F(floor(n/i))=1;

     令i等于1时,变形可以得到F(n)=1-\sum_{i=2}^nF(floor(n/i))

     然后,整除分块递归+记忆化搜索就行。

     接着我们来说一说\sum_{i=1}^n \varphi(i)怎么求

     还是设G(n)=\sum_{i=1}^n \varphi(i)

     因为有\varphi*1=id

     所以得出两条式子:

     \sum_{k=1}^n (\varphi*1)(k)=\sum_{i=1}^n1(i)\sum_{j=1}^{floor(n/i)}\varphi(j)=\sum_{i=1}^nQ(floor(n/i))

     \sum_{k=1}^n(\varphi*1)(k)=\sum_{k=1}^n id(i)=\frac{(1+n)*n}{2}

     两式相等,所以\sum_{i=1}^nQ(floor(n/i))=\frac{(1+n)*n}{2}

     那么Q(n)=\frac{(1+n)*n}{2}-\sum_{i=2}^nQ(floor(n/i))

     也是递归,整除分块即可。

     可以通过复杂的证明得到,上述递归算法的时间复杂度是O(n^{\frac{3}{4}}),那么就可以处理一个较大的n(约为int)的求和。

    优化

       其实是可以优化的,就是对于一个bound以下的数直接线性筛预处理,然后如果当前n<=bound,那么直接返回结果。

       否则计算答案记录下之后再返回结果,又通过一系列复杂的证明可以知道,当bound=n^{\frac{2}{3}}时,有最优时间复杂度O(n^{\frac{2}{3}})

    代码

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<map>
using namespace std;

const int bound=5e6;
map<int , long long> summ,sump;//sum of \mu and sum of \varphi
long long sum1[bound+10],sum2[bound+10]; 
int p[bound+10],temp;
bool vis[bound+10];

long long get_sum1(int x){
	if(x<=bound) return sum1[x];
	if(summ[x]) return summ[x];
	int l=2,r;
	long long ans=1;
	while(l<=x){
		r=x/(x/l);
		ans-=(r-l+1)*get_sum1(x/l);
		l=r+1;
	}
	return summ[x]=ans;
}

long long get_sum2(int x){
	if(x<=bound) return sum2[x];
	if(sump[x]) return sump[x];
	int l=2,r;
	long long ans=(long long)(x+1)*x/2;
	while(l<=x){
		r=x/(x/l);
		ans-=(r-l+1)*get_sum2(x/l);
		l=r+1;
	}
	return sump[x]=ans;
}

int main(){
	sum1[1]=sum2[1]=1;
	for(int i=2;i<=bound;i++){
		if(!vis[i]) {p[++p[0]]=i;sum1[i]=-1;sum2[i]=i-1;}
		for(int j=1;j<=p[0] && (temp=i*p[j])<=bound;j++){
			vis[temp]=true;
			if(i%p[j]==0) {sum2[temp]=sum2[i]*p[j];break;}
			sum1[temp]=-sum1[i];
			sum2[temp]=sum2[i]*(p[j]-1);
		}
		sum1[i]+=sum1[i-1];
		sum2[i]+=sum2[i-1];
	}
	static int T,n;
	scanf("%d",&T);
	while(T--){
		scanf("%d",&n);
		printf("%lld %lld\n",get_sum2(n),get_sum1(n));
	}
}

   进阶

      假设我们现在要求\sum_{i=1}^{n}i*\mu(i)呢?

      上面的方法好像就行不通了。

      考虑令f(i)=id(i)\dot\ \mu(i)这里指的是乘法。

      那么问题变为求\sum_{i=1}^n f(i)

      我们令id(i)=id(i)\dot\ 1(i)

      又因为f(i)=id(i)\dot\ \mu(i)

      所以左右两边同时卷积起来得到(id*f)(i)=(1*\mu)(i)\dot\ id(i)

      注意,这里不是id(i)^2,而是id(i),因为id是一个完全积性函数,两个相卷就相当于将id(i)提出来。

      然后再令F(n)=\sum_{i=1}^n f(i)

      所以\sum_{k=1}^n (id*f)(k)=\sum_{i=1}^n iF(floor(n/i))

      等于\sum_{k=1}^{n}(\mu*1)(k)=\sum_{k=1}^ne(i)=1

      所以\sum_{i=1}^niF(floor(n/i))=1

      移项得到:F(n)=1-\sum_{i=2}^niF(floor(n/i))

      然后和上面就一样了。

      发现没有,我们处理一个熟悉的数论函数前面乘上了一个完全积性函数来求和的时候,我们可以通过这种构造方法来使得和很好求。

      

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值