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

题面

题目传送门

解法

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

  • 首先我们先考虑一下 d ( i j ) 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] ,具体证明可以看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]
  • 感觉后面的整除并不是那么方便,不妨把 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]
  • 然后就变成比较熟悉的式子了,反演一下可以得到 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)
  • 有一个比较简单的结论: n x y = n x y \lfloor\frac{\lfloor\frac{n}{x}\rfloor}{y}\rfloor=\lfloor\frac{n}{xy}\rfloor ,那么我们就可以令 n = n d , m = m d n'=\lfloor\frac{n}{d}\rfloor,m'=\lfloor\frac{m}{d}\rfloor 。然后式子就变成 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)
  • s [ n ] = i = 1 n d ( i ) s[n]=\sum_{i=1}^nd(i) ,然后我们可以发现 i = 1 n n i = s [ n ] \sum_{i=1}^n\lfloor\frac{n}{i}\rfloor=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]
  • 那么我们发现就可以数论分块了,我们只需要预处理出 s s 数组和 μ \mu 的前缀和就可以了
  • 时间复杂度: O ( T n ) O(T\sqrt n)

代码

#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;
}

猜你喜欢

转载自blog.csdn.net/emmmmmmmmm/article/details/84498211