「题解」[SDOI2015]约数个数和


link
又到了喜闻乐见的推式子环节


本题唯一的难点-引理\(d(ij)=\sum_{u|i}\sum_{v|j}[\gcd(u,v)=1]\)
证明:对于 \(ij\) 因数 \(k\),令 \(c\)\(k\) 唯一分解后某一个 \(prime\) 的指数,\(a\)\(i\) 的指数,\(b\)\(j\) 的指数。我们 钦定 \(a\) 优先贡献 \(c\),剩下不够的用 \(b\) 补。那么一个 \(k\) 对应唯一的贡献情况,一种贡献情况也对应唯一的一个 \(k\)

对于 \(c\leqslant a\) 情况,我们又 钦定 给甲方(\(i\))乘上 \(prime^c\) 的价值。对于 \(c>a\)钦定 给乙方(\(j\))乘上 \(prime^{c-a}\) 的价值。这样两边的价值肯定互质,且 \(k\) 也是一一对应,不重不漏。容易发现两个价值就是原式的含义。得证。


但是 OI 题不是证明题。考虑如果我们原先不知道这个式子,怎么看出来。首先看如果 \(ij\) 有个因数为 \(6\),那我可以 \(i=2,j=3\),也可以 \(i=3,j=2\),这可不好搞。

既然反正这个 \(6\) 都只能算一次,我们就只看成 \(2\)\(3\)\(i\) 提供的贡献。那么这样 \(j\) 可不认啊,你 \(i\) 有可能自己都不够这么多,凭什么提供这么多贡献。但是只给 \(j\) 也不行。

于是就自然想到 \(i,j\) 以一个规则提供贡献,很容易想到 \(i\) 先补完(情况 1),不够的 \(j\) 来补(情况 2)。

然后你想情况1的 \(j\) 不提供贡献和情况2的 \(i\) 全部提供完是一个东西。那么可以将情况2的 \(i\) 也视作没提供贡献。那么两边选的数就互质。那么稍微延伸一下这个式子就 yy 出来了。


然后就比较简单了。
\(\sum_{i=1}^n\sum_{j=1}^m\sum_{u|i}\sum_{v|j}[\gcd(u,v)=1]\)
推式子常用技巧:把因数转化成倍数。?
\(=\sum_{u=1}^n\sum_{v=1}^m \lfloor \frac n u \rfloor \lfloor \frac m v \rfloor[\gcd(u,v)=1]\)
此时两种思考,第一种是 Mobius 反演,第二种是直接利用 Mobius 函数的性质。这里展开讲第二种。
\(=\sum_{u=1}^n\sum_{v=1}^m \lfloor \frac n u \rfloor \lfloor \frac m v \rfloor\sum_{w|\gcd(u,v)}\mu_w\)
\(=\sum_{w=1}^{\min(n,m)}\mu_w\sum_{u=1}^{n/w}\sum_{v=1}^{m/w}\lfloor \frac n {wu} \rfloor \lfloor \frac m {wv} \rfloor\)
然后这道题就做完了。容易想到将 \(n/w\) 以及 \(m/w\) 整除分块,预处理前缀和搞一搞即可。

#include 
#include 
#include 
#include 
#define LL long long
using namespace std;
const int MAXN = 5e4 + 5;
int prime[MAXN / 10], cnt, mu[MAXN], n, m;
LL p[MAXN];
bool vis[MAXN];
void primes() {
	mu[1] = 1;
	for(int i = 2; i <= MAXN - 5; i ++) {
		if(vis[i] == 0) prime[++ cnt] = i, mu[i] = -1;
		for(int j = 1; j <= cnt && prime[j] * i <= MAXN - 5; j ++) {
			vis[i * prime[j]] = 1;
			if(i % prime[j] == 0) {
				mu[i * prime[j]] = 0; break;
			}
			mu[i * prime[j]] = -mu[i];
		}
	}
	for(int i = 1; i <= MAXN - 5; i ++) mu[i] += mu[i - 1];
}
int T;
LL Calc(int x, int y) {
	LL res = 0;
	for(int l = 1, r; l <= x && l <= y; l = r + 1) {
		r = min(x / (x / l), y / (y / l));
		res += 1LL * (mu[r] - mu[l - 1]) * p[x / l] * p[y / l];
	}	
	return res;
}
LL calc(int x) {
	LL res = 0;
	for(int l = 1, r; l <= x; l = r + 1) {
		r = x / (x / l); res += (r - l + 1) * 1LL * (x / l);
	}
	return res;
}
int main() {
	primes();
	for(int i = 1; i <= 50000; i ++) p[i] = calc(i);
	for(scanf("%d", &T); T; T --) {
		scanf("%d%d", &n, &m); printf("%lld\n", Calc(n, m));
	}
	return 0;
}