【XSY3804】QQ数(莫比乌斯函数,容斥)

题面

在这里插入图片描述

题解

明显地,这个QQ数可以用 μ \mu μ 表示,于是询问就变成了这样:

∑ i = 1 n ∑ d ∣ i ( 1 − μ ( d ) 2 ) = ∑ d = 1 n ⌊ n d ⌋ ( 1 − μ ( d ) 2 ) \begin{aligned} & \sum_{i=1}^n\sum_{d|i}\left(1-\mu(d)^2\right)\\ =& \sum_{d=1}^n\left\lfloor\frac{n}{d}\right\rfloor\left(1-\mu(d)^2\right) \end{aligned} =i=1ndi(1μ(d)2)d=1ndn(1μ(d)2)
发现 ⌊ n d ⌋ \lfloor\dfrac{n}{d}\rfloor dn 是可以整除分块的,但是 n ≤ 1 0 9 n\leq 10^9 n109 无法线性筛 μ \mu μ

考虑如何更快地求出 ∑ i = 1 n ( 1 − μ ( d ) 2 ) \sum\limits_{i=1}^n\left(1-\mu(d)^2\right) i=1n(1μ(d)2),也就是 1 ∼ n 1\sim n 1n 中的QQ数个数。

但是有这么一个神奇的式子:
∑ i = 1 n ( 1 − μ ( i ) 2 ) = ∑ x = 2 n − μ ( x ) ⌊ N x 2 ⌋ \sum_{i=1}^n\left(1-\mu(i)^2\right)=\sum_{x=2}^{\sqrt n}-\mu(x)\lfloor\frac{N}{x^2}\rfloor i=1n(1μ(i)2)=x=2n μ(x)x2N
证明:

首先,对于每一个QQ数 a a a,他肯定可以拆分成这种形式: a = x 2 y a=x^2 y a=x2y(其中 x x x 不是QQ数),也就是说,我们把每一个QQ数表示成一个非QQ数的平方再乘上一个数。

比如QQ数 2 4 × 3 3 × 5 2^4\times 3^3\times 5 24×33×5 可以拆分为 ( 2 × 3 ) 2 × … (2\times 3)^2\times \dots (2×3)2× x = 2 × 3 x=2\times 3 x=2×3)或者 2 2 × … 2^2\times \dots 22× x = 2 x=2 x=2)或者 3 2 × … 3^2\times \dots 32× x = 3 x=3 x=3)。

那么 ∑ x = 2 n − μ ( x ) ⌊ N x 2 ⌋ \sum\limits_{x=2}^{\sqrt n}-\mu(x)\lfloor\dfrac{N}{x^2}\rfloor x=2n μ(x)x2N 就可以看成是枚举每一个 x x x,然后再枚举有多少个 a a a,然后再配上一个 − μ ( x ) -\mu(x) μ(x) 去重。(至于能去重的原因结合 μ \mu μ 的定义以及这个式子想一想: ∑ i = 1 n ( − 1 ) i ( n i ) = − 1 \sum\limits_{i=1}^n(-1)^i\dbinom{n}{i}=-1 i=1n(1)i(in)=1

那么就能在 n \sqrt n n 的时间内求出 ∑ i = 1 n ( 1 − μ ( d ) 2 ) \sum\limits_{i=1}^n\left(1-\mu(d)^2\right) i=1n(1μ(d)2) 了。

时间复杂度比较玄学,反正能过。

代码如下:

#include<bits/stdc++.h>

#define SN 32000
#define ll long long

using namespace std;

int ql,qr;
int cnt,prime[SN],mu[SN];
bool notprime[SN];

void init()
{
	mu[1]=1;
	for(int i=2;i<=31622;i++)
	{
		if(!notprime[i])
		{
			prime[++cnt]=i;
			mu[i]=-1;
		}
		for(int j=1;j<=cnt&&i*prime[j]<=31622;j++)
		{
			notprime[i*prime[j]]=1;
			if(!(i%prime[j])) break;
			mu[i*prime[j]]=-mu[i];
		}
	}
}

int summu(int n)
{
	ll ans=0;
	for(int i=2;i*i<=n;i++)
		ans-=mu[i]*(n/i/i);
	return ans;
}

ll solve(int n)
{
	ll ans=0;
	for(int l=1,r,last=0;l<=n;l=r+1)
	{
		r=n/(n/l);
		ll tmp=summu(r);
		ans+=(n/l)*(tmp-last);
		last=tmp;
	}
	return ans;
}

int main()
{
	init();
	scanf("%d%d",&ql,&qr);
	printf("%lld\n",solve(qr)-solve(ql-1));
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值