bzoj 3994 [SDOI2015]约数个数和 莫比乌斯反演

题面

题目传送门

解法

其实很久之前就想写这道题了……

  • 首先我们先考虑一下 d ( i j ) d(ij) d(ij)怎么处理。
  • 有一个结论: d ( i j ) = ∑ x ∣ i ∑ y ∣ j [ g c d ( x , y ) = = 1 ] d(ij)=\sum_{x|i}\sum_{y|j}[gcd(x,y)==1] d(ij)=xiyj[gcd(x,y)==1],具体证明可以看popoqqq大爷的证明:链接
  • 然后我们就可以把式子写成这样: ∑ i = 1 n ∑ j = 1 m ∑ x ∣ i ∑ y ∣ j [ g c d ( x , y ) = = 1 ] \sum_{i=1}^n\sum_{j=1}^m\sum_{x|i}\sum_{y|j}[gcd(x,y)==1] i=1nj=1mxiyj[gcd(x,y)==1]
  • 感觉后面的整除并不是那么方便,不妨把 x , y x,y x,y提前到前面枚举,就变成: ∑ x = 1 n ∑ y = 1 m ⌊ n x ⌋ ⌊ m y ⌋ [ g c d ( x , y ) = = 1 ] \sum_{x=1}^n\sum_{y=1}^m\lfloor\frac{n}{x}\rfloor\lfloor\frac{m}{y}\rfloor[gcd(x,y)==1] x=1ny=1mxnym[gcd(x,y)==1]
  • 然后就变成比较熟悉的式子了,反演一下可以得到 ∑ d μ ( d ) ( ∑ i = 1 ⌊ n d ⌋ ⌊ n i d ⌋ ) ( ∑ j = 1 ⌊ m d ⌋ ⌊ m j d ⌋ ) \sum_d\mu(d)(\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\lfloor\frac{n}{id}\rfloor)(\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}\lfloor\frac{m}{jd}\rfloor) dμ(d)(i=1dnidn)(j=1dmjdm)
  • 有一个比较简单的结论: ⌊ ⌊ n x ⌋ y ⌋ = ⌊ n x y ⌋ \lfloor\frac{\lfloor\frac{n}{x}\rfloor}{y}\rfloor=\lfloor\frac{n}{xy}\rfloor yxn=xyn,那么我们就可以令 n ′ = ⌊ n d ⌋ , m ′ = ⌊ m d ⌋ n'=\lfloor\frac{n}{d}\rfloor,m'=\lfloor\frac{m}{d}\rfloor n=dn,m=dm。然后式子就变成 ∑ d μ ( d ) ( ∑ i = 1 n ′ ⌊ n ′ i ⌋ ) ( ∑ j = 1 m ′ ⌊ m ′ j ⌋ ) \sum_d\mu(d)(\sum_{i=1}^{n'}\lfloor\frac{n'}{i}\rfloor)(\sum_{j=1}^{m'}\lfloor\frac{m'}{j}\rfloor) dμ(d)(i=1nin)(j=1mjm)
  • s [ n ] = ∑ i = 1 n d ( i ) s[n]=\sum_{i=1}^nd(i) s[n]=i=1nd(i),然后我们可以发现 ∑ i = 1 n ⌊ n i ⌋ = s [ n ] \sum_{i=1}^n\lfloor\frac{n}{i}\rfloor=s[n] i=1nin=s[n]。这个式子其实也挺好证的,只要看每一个因子的贡献就可以了。
  • 那么式子就变成了 ∑ d μ ( d ) s [ ⌊ n d ⌋ ] s [ ⌊ m d ⌋ ] \sum_d\mu(d)s[\lfloor\frac{n}{d}\rfloor]s[\lfloor\frac{m}{d}\rfloor] dμ(d)s[dn]s[dm]
  • 那么我们发现就可以数论分块了,我们只需要预处理出 s s s数组和 μ \mu μ的前缀和就可以了
  • 时间复杂度: O ( T n ) O(T\sqrt n) O(Tn )

代码

#include <bits/stdc++.h>
#define ll long long
#define N 50010
using namespace std;
template <typename T> void chkmax(T &x, T y) {x = x > y ? x : y;}
template <typename T> void chkmin(T &x, T y) {x = x > y ? y : x;}
template <typename T> void read(T &x) {
	x = 0; int f = 1; char c = getchar();
	while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
	while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
bool f[N]; int p[N];
ll d[N], mu[N];
void sieve(int n) {
	memset(f, true, sizeof(f)); int len = 0; mu[1] = 1;
	for (int i = 1; i <= n; i++)
		for (int j = i; j <= n; j += i)
			d[j]++;
	for (int i = 2; i <= n; i++) {
		if (f[i]) p[++len] = i, mu[i] = -1;
		for (int j = 1; j <= len && i * p[j] <= n; j++) {
			int k = i * p[j]; f[k] = false;
			if (i % p[j] == 0) {mu[k] = 0; break;}
			mu[k] = -mu[i];
		}
	}
	for (int i = 1; i <= n; i++) d[i] += d[i - 1], mu[i] += mu[i - 1];
}
ll solve(int n, int m) {
	ll ret = 0, x = 0;
	for (int i = 1; i <= n; i = x + 1) {
		x = min(n / (n / i), m / (m / i));
		ret += 1ll * d[n / i] * d[m / i] * (mu[x] - mu[i - 1]);
	}
	return ret;
}
int main() {
	sieve(5e4); int T; read(T);
	while (T--) {
		int n, m; read(n), read(m);
		if (n > m) swap(n, m);
		cout << solve(n, m) << "\n";
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值