P3327 [SDOI2015]约数个数和

莫比乌斯反演在数论求和问题中的应用
这篇博客介绍了莫比乌斯反演在解决数论中的求和问题上的应用,通过数学推导展示了如何将原始问题转化为更简单的形式。文章详细解释了从d(ij)的定义出发,如何利用反演公式进行化简,并最终得到求和表达式的转换过程。算法的时间复杂度为O(n+T√n),适用于解决大规模数据的问题。

Label

莫比乌斯反演

Description

d(x)d(x)d(x)xxx的约数个数,给定TTTn,m(1≤T,n,m≤5×104)n,m(1\le T,n,m\le 5\times 10^4)n,m(1T,n,m5×104),对于每组n,mn,mn,m,求

∑i=1n∑j=1md(ij)\sum_{i=1}^{n}\sum_{j=1}^{m}d(ij)i=1nj=1md(ij)

Solution

为了将ddd变化为可利用反演结论化简的式子,我们首先引入一个结论:

d(ij)=∑x∣i∑y∣j[gcd(i,j)=1]d(ij)=\sum_{x|i}\sum_{y|j}[gcd(i,j)=1]d(ij)=xiyj[gcd(i,j)=1] (1)(1)(1)

该结论中出现了[gcd(i,j)=1][gcd(i,j)=1][gcd(i,j)=1]的形式,我们考虑进一步变换:

d(ij)=∑x∣i∑y∣j[gcd(i,j)=1]d(ij)=\sum_{x|i}\sum_{y|j}[gcd(i,j)=1]d(ij)=xiyj[gcd(i,j)=1]

=∑x∣i∑y∣j∑p∣gcd(x,y)μ(p)=\sum_{x|i}\sum_{y|j}\sum_{p|gcd(x,y)}\mu(p)=xiyjpgcd(x,y)μ(p)

=∑p∣i∧p∣jμ(p)∑x∣i∑y∣j[p∣gcd(x,y)]=\sum_{p|i\wedge p|j}\mu(p)\sum_{x|i}\sum_{y|j}[p|gcd(x,y)]=pipjμ(p)xiyj[pgcd(x,y)]

=∑p∣i∧p∣jμ(p)∑xp∣ip∑yp∣jp[1∣gcd(xp,yp)]=\sum_{p|i\wedge p|j}\mu(p)\sum_{\frac{x}{p}|\frac{i}{p}}\sum_{\frac{y}{p}|\frac{j}{p}}[1|gcd(\frac{x}{p},\frac{y}{p})]=pipjμ(p)pxpipypj[1gcd(px,py)] (2)(2)(2)

=∑p∣i∧p∣jμ(p)∑xp∣ip∑yp∣jp1=\sum_{p|i\wedge p|j}\mu(p)\sum_{\frac{x}{p}|\frac{i}{p}}\sum_{\frac{y}{p}|\frac{j}{p}}1=pipjμ(p)pxpipypj1

=∑p∣i∧p∣jμ(p)∑x∣ip1∑y∣jp1=\sum_{p|i\wedge p|j}\mu(p)\sum_{x|\frac{i}{p}}1\sum_{y|\frac{j}{p}}1=pipjμ(p)xpi1ypj1

=∑p∣i∧p∣jμ(p)d(ip)d(jp)=\sum_{p|i\wedge p|j}\mu(p)d(\frac{i}{p})d(\frac{j}{p})=pipjμ(p)d(pi)d(pj)

∴∑i=1n∑j=1md(ij)\therefore \sum_{i=1}^{n}\sum_{j=1}^{m}d(ij)i=1nj=1md(ij)

=∑i=1n∑j=1m∑x∣i∑y∣j[gcd(i,j)=1]=\sum_{i=1}^{n}\sum_{j=1}^{m}\sum_{x|i}\sum_{y|j}[gcd(i,j)=1]=i=1nj=1mxiyj[gcd(i,j)=1]

=∑i=1n∑j=1m=∑p∣i∧p∣jμ(p)d(ip)d(jp)=\sum_{i=1}^{n}\sum_{j=1}^{m}=\sum_{p|i\wedge p|j}\mu(p)d(\frac{i}{p})d(\frac{j}{p})=i=1nj=1m=pipjμ(p)d(pi)d(pj)

=∑p=1min(n,m)μ(p)∑i=1n∑j=1m[p∣gcd(i,j)]d(ip)d(jp)=\sum_{p=1}^{min(n,m)}\mu(p)\sum_{i=1}^{n}\sum_{j=1}^{m}[p|gcd(i,j)]d(\frac{i}{p})d(\frac{j}{p})=p=1min(n,m)μ(p)i=1nj=1m[pgcd(i,j)]d(pi)d(pj) (3)(3)(3)

=∑p=1min(n,m)μ(p)∑i=1n∑j=1m[1∣gcd(ip,jp)]d(ip)d(jp)=\sum_{p=1}^{min(n,m)}\mu(p)\sum_{i=1}^{n}\sum_{j=1}^{m}[1|gcd(\frac{i}{p},\frac{j}{p})]d(\frac{i}{p})d(\frac{j}{p})=p=1min(n,m)μ(p)i=1nj=1m[1gcd(pi,pj)]d(pi)d(pj)

=∑p=1min(n,m)μ(p)∑i=1⌊np⌋∑j=1⌊mp⌋d(i)d(j)=\sum_{p=1}^{min(n,m)}\mu(p)\sum_{i=1}^{\lfloor\frac{n}{p}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{p}\rfloor}d(i)d(j)=p=1min(n,m)μ(p)i=1pnj=1pmd(i)d(j)

=∑p=1min(n,m)μ(p)S(⌊np⌋)S(⌊mp⌋)=\sum_{p=1}^{min(n,m)}\mu(p)S(\lfloor\frac{n}{p}\rfloor)S(\lfloor\frac{m}{p}\rfloor)=p=1min(n,m)μ(p)S(pn)S(pm)

(其中S(x)=∑k=1x(d(k))S(x)=\sum_{k=1}^{x}(d(k))S(x)=k=1x(d(k))

算法时间复杂度O(n+Tn)O(n+T\sqrt n)O(n+Tn)

总结

注释:

(1)积累结论:d(ij)=∑x∣i∑y∣j[gcd(i,j)=1]d(ij)=\sum_{x|i}\sum_{y|j}[gcd(i,j)=1]d(ij)=xiyj[gcd(i,j)=1]

(2)与将gcd(x,y)=dgcd(x,y)=dgcd(x,y)=d转化为gcd(xd,yd)gcd(\frac{x}{d},\frac{y}{d})gcd(dx,dy)的套路类似,遇到形似[x∣y][x|y][xy]时,可考虑将其转化为[1∣yx][1|\frac{y}{x}][1xy],当xxx整除yyy时此真值式恒为1。

(3)求和符号交换时,需考虑合适的将被交换的求和符号对应的范围转化的方式:有时考虑所有合法值的取值范围,有时转化为真值式[...][...][...]表示更加简洁。

Code

#include<cstdio>
#include<iostream>
#define ri register int
#define ll long long
using namespace std;

const int MAXN=5e4;
int T,N,M,n,cnt;
ll ans,mu[MAXN+20],prime[MAXN+20],sum[MAXN+20],d[MAXN+20];
bool notprime[MAXN+20];

void Mobius() 
{
	mu[1]=1,notprime[1]=true;
	for(ri i=2;i<=MAXN;++i)
	{
		if(!notprime[i]) prime[++cnt]=i,mu[i]=-1;
		for(ri j=1;j<=cnt&&i*prime[j]<=MAXN;++j)
		{
			notprime[i*prime[j]]=true;
			if(i%prime[j]==0) break;
			else mu[i*prime[j]]=-mu[i];
		}
	}
	for(ri i=1;i<=MAXN;++i) sum[i]=sum[i-1]+mu[i];
}

void GetD()
{
	for(ri i=1;i<=MAXN;++i)
		for(ri j=i;j<=MAXN;j+=i) ++d[j];
	for(ri i=1;i<=MAXN;++i) d[i]=d[i-1]+d[i];
}

int main()
{
	std::ios::sync_with_stdio(false);
	cin>>T;
	Mobius(); GetD();
	for(ri op=1;op<=T;++op)
	{
		cin>>N>>M;
		n=min(N,M),ans=0LL;
		for(ri l=1,r;l<=n;l=r+1)
		{
			r=min(N/(N/l),M/(M/l));
			ans+=(sum[r]-sum[l-1])*d[N/l]*d[M/l];
		}
		cout<<ans<<'\n';
	}
	return 0;
}
### 计算一个整数的约数个数及所有约数 要计算一个整数的约数个数及其所有约数,可以采用两种不同的方法:**暴力枚举法****质因数分解法**。后者在处理大整数时效率更高。 #### 暴力枚举法 对于一个正整数 $ n $,可以通过遍历从 1 到 $ \sqrt{n} $ 的所有整数来判断是否是其约数。 - **约数个数**:每找到一个小于 $ \sqrt{n} $ 的因数 $ i $,若 $ i \neq n/i $,则 $ n $ 同时具有 $ i $ $ n/i $ 两个因数,因此每次找到因数时可以将计数加 2;若 $ i = n/i $,则只加 1。 - **约数**:同理,每次找到因数时,将 $ i $ $ n/i $(如果不同)加入总中。 ```c #include <stdio.h> #include <math.h> int main() { int n, i, count = 0, sum = 0; printf("请输入一个整数:"); scanf("%d", &n); for (i = 1; i <= sqrt(n); i++) { if (n % i == 0) { if (i == n / i) { count += 1; sum += i; } else { count += 2; sum += i + n / i; } } } printf("约数个数:%d\n", count); printf("约数:%d\n", sum); return 0; } ``` 该方法适用于较小的整数,时间复杂度为 $ O(\sqrt{n}) $。 #### 质因数分解法 若已知整数 $ n $ 的质因数分解为: $$ n = p_1^{a_1} \cdot p_2^{a_2} \cdot \ldots \cdot p_k^{a_k} $$ 则: - **约数个数**:$$ \text{count} = (a_1 + 1)(a_2 + 1)\ldots(a_k + 1) $$ - **约数**:$$ \text{sum} = (1 + p_1 + p_1^2 + \ldots + p_1^{a_1})(1 + p_2 + p_2^2 + \ldots + p_2^{a_2})\ldots(1 + p_k + p_k^2 + \ldots + p_k^{a_k}) $$ 例如,对于 $ n = 20 = 2^2 \cdot 5^1 $,其约数个数为 $ (2+1)(1+1) = 6 $,约数为 $ (1+2+4)(1+5) = 7 \cdot 6 = 42 $ [^2]。 该方法适用于已知质因数分解的场景,计算效率更高。 #### 示例:质因数分解后计算 ```python def divisor_count_and_sum(n): count = 1 sum_divisors = 1 i = 2 while i * i <= n: exponent = 0 while n % i == 0: n //= i exponent += 1 count *= (exponent + 1) sum_powers = 0 for j in range(exponent + 1): sum_powers += i ** j sum_divisors *= sum_powers i += 1 if n > 1: count *= 2 sum_divisors *= (1 + n) return count, sum_divisors # 示例 print(divisor_count_and_sum(20)) # 输出:(6, 42) ``` ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值