「51nod1220」 约数之和

复盘 \(Bindir0\) 讲的数论清真题,写一篇题解来祸害自己。

Description

给定 \(n\) ,求

\[\sum_{i = 1} ^ {n} \sum_{j = 1} ^ {n} \sigma(i \cdot j) \]

结果对 \(10 ^ 9 + 7\) 取模。

\(2\leq n \leq 10 ^ 9\)

Analysis

无脑转化式子+打表+无脑乱卷函数=不用推式子!

Solution

按照一贯的套路,把 \(\sigma\) 换一种写法,但是注意 \(\sigma\) 注意有可能会算重,所以要限制枚举,要求两个因数相对独立:

\[\sum_{i = 1} ^ {n} \sum_{j = 1} ^ {n} \sum_{p | i} \sum_{q | j} p \cdot q [i / p \perp q] \]

写法不太好看,改成:

\[\sum_{i = 1} ^ {n} \sum_{j = 1} ^ {n} \sum_{p | i} \sum_{q | j} i / p \cdot q [p \perp q] \]

然后是另外一个常见的套路,就是交换枚举顺序,注意下面的 \(i \cdot p\) 就是对应上面的 \(i\) ,要清楚枚举东西的含义:

\[\sum_{p = 1} ^ {n} \sum_{q = 1} ^ {n} q / p \cdot [p \perp q] \sum_{i = 1} ^ {\lfloor n / p \rfloor} i \cdot p \sum_{j = 1} ^ {\lfloor n / q \rfloor} 1 \]

稍稍化简一下,把 \(p\) 消掉,然后后面两坨可以直接写成公式的样子,稍微换一下位置:

\[\sum_{p = 1} ^ {n} \frac{\lfloor n / p \rfloor \cdot (\lfloor n / p \rfloor + 1)}{2} \sum_{q = 1} ^ {n} \lfloor n / q \rfloor \cdot q \cdot [p \perp q] \]

感觉最后面那个限制让我们很难放开手脚去把 \(p\)\(q\) 分开算,据说是有两种做法,但因为本人能力有限,就先说一下莫反的做法。

以限制条件作为基础,可以很自然地写出两个函数形如:

\[f(k) = \sum_{p = 1} ^ {n} \frac{\lfloor n / p \rfloor \cdot (\lfloor n / p \rfloor + 1)}{2} \sum_{q = 1} ^ {n} \lfloor n / q \rfloor \cdot q \cdot [\gcd(p, q) = k]\\ g(k) = \sum_{p = 1} ^ {n} \frac{\lfloor n / p \rfloor \cdot (\lfloor n / p \rfloor + 1)}{2} \sum_{q = 1} ^ {n} \lfloor n / q \rfloor \cdot q \cdot [k | \gcd(p, q)] \]

然后很好写莫反的式子:

\[g(k) = \sum_{d | k} f(d)\\ f(k) = \sum_{k | d} \mu(\frac{d}{k}) \cdot g(d) \]

因为我们最终要求的答案就是 \(f(1)\) ,所以就把函数带进去,同时如我们要让最后的式子能通过什么递推算,还是把后面的那两坨和 \(d\) 没有关系的式子硬提出来一个 \(d\) ,然后注意保持枚举的东西的一致性:

\[ans = f(1) = \sum_{d} \mu(d) \cdot g(d)\\ \Longrightarrow ans = f(1) = \sum_{d} \mu(d) \cdot \sum_{p = 1} ^ {\lfloor n / d \rfloor} \frac{\lfloor n / pd \rfloor \cdot (\lfloor n / pd \rfloor + 1)}{2} \sum_{q = 1} ^ {\lfloor n / d \rfloor} \lfloor n / qd \rfloor \cdot qd \]

最后面的 \(d\) 可以甩到前面去,但就不单独列出来了。然后来看 \(p\) 这一层的式子大概是个什么鬼。

\[\sum_{p = 1} ^ {n} \frac{\lfloor n / p \rfloor \cdot (\lfloor n / p \rfloor + 1)}{2}\\ \Longrightarrow \sum_{p = 1} ^ {n} \sum_{i = 1}^{\lfloor n / p \rfloor} i\\ \]

其实这样已经化不下去了,那就继续用老套路,调换枚举顺序,算 \(i\) 出现的次数:

\[\Longrightarrow \sum_{i = 1} ^ {n} \sum_{\lfloor n / p \rfloor \geq i} i\\ \]

这样还是不行,因为 \(p\) 没有单独分出来,但是发现下取整对应大于,放缩一下可以得到:

\[\Longrightarrow \sum_{i = 1} ^ {n} \sum_{\lfloor n / i \rfloor \geq p} i\\ \Longrightarrow \sum_{i = 1} ^ {n} \lfloor n / i \rfloor \cdot i\\ \]

然后你发现这和后面那坨不谋而合,所以快乐合并:

\[ans = f(1) = \sum_{d} \mu(d) \cdot d \cdot (\sum_{i = 1} ^ {\lfloor n / d \rfloor} \lfloor n / i d \rfloor \cdot i) ^ 2 \]

又不快乐了,因为后面那坨压根和常用函数没关系,虽然没有约束条件,可以放开手脚了,但是卷积卷不起来呀。。

于是又回头来看刚刚推得的式子,可以考虑考虑他的含义,等于是说这个范围内包含因子 \(i\) 的数一共有 \(\lfloor n / i \rfloor\) 个,不重,因为每个因子都枚举到了,不漏,那合到一起不就刚好是 \(\sum\sigma\) 吗:

\[\sum_{i = 1} ^ {n} \lfloor n / i \rfloor \cdot i\\ \Longrightarrow \sum_{i = 1} ^ {n} \sigma (i)\\ \therefore ans = f(1) = \sum_{d} \mu(d) \cdot d \cdot \Big(\sum_{i = 1} ^ {\lfloor n / d \rfloor} \sigma(i) \Big) ^ 2 \]

终于该筛法上场了,但是因为本人水平有限,只会杜教筛的做法,min25筛可能要等到千年以后了。。

所以这样来看现在我们就要卷两个东西,一个是 \(\mu \cdot id\) ,另一个是 \(\sigma\)

前者的话我们直接选一个 \(id\) 卷上去,正好能把原式里面的 \(id\) 卷掉:

\[((\mu \cdot id) * id)(n) = \sum_{d | n} \mu(d) \cdot d \cdot \frac{n}{d}\\ \Longrightarrow \sum_{d | n} \mu(d) \cdot n \Rightarrow [n = 1] \]

嗯,好极了!

后者的话乍一看并不知道有什么常用公式能套上去,但是,我们知道根据定义,有这样几个东西:

\[\mu * I = [n = 1] \\ \sigma = id * I\\ \]

目标乍现!!

所以 \(\sigma * \mu = id\) !!

该卷啥都已经清楚了,直接套杜教筛的板子就可以了。

注意线性筛预处理 \(\sigma\) 的时候要在多存一个数组记录一个数是被哪个质数筛中的,这样才能消去不互质的两个数重复的因子,从而才能合并。

Code

/*

*/
#include<bits/stdc++.h>
#define ri register int
using namespace std;
typedef long long ll;
const int N = 1e6 + 10, M = 1e6;
const ll mod = 1e9 + 7, inv2 = 500000004, inv6 = 166666668;
ll mud[N], mu[N], sig[N], zs[N];
int n, pri[N], tot; bool vis[N];
unordered_map<int, ll> ans_mud, ans_mu, ans_sig;
inline ll read() {
	char ch = getchar();
	ll s = 0, w = 1;
	while (ch < '0' || ch > '9') {if (ch == '-') w = -1; ch = getchar();}
	while (ch >= '0' && ch <= '9') {s = (s << 3) + (s << 1) + ch - '0'; ch = getchar();}
	return s * w;
}
inline void init() {
	mu[1] = sig[1] = 1;
	for (ri i = 2; i <= M; ++i) {
		if (!vis[i]) pri[++tot] = i, mu[i] = -1, sig[i] = zs[i] = i + 1;
		for (ri j = 1; j <= tot && i * pri[j] <= M; ++j) {
			vis[i * pri[j]] = 1;
			if (i % pri[j] == 0) {
				zs[i * pri[j]] = zs[i] * pri[j] + 1;
				sig[i * pri[j]] = sig[i] / zs[i] * zs[i * pri[j]];
				break;
			}
			mu[i * pri[j]] = -mu[i];
			zs[i * pri[j]] = pri[j] + 1;
			sig[i * pri[j]] = sig[i] * zs[i * pri[j]];
		}
	}
	for (ll i = 1; i <= M; ++i) {
		mud[i] = i * mu[i] % mod;
		mud[i] = (mud[i] + mud[i - 1]) % mod;
		mu[i] = (mu[i] + mu[i - 1]) % mod;
		sig[i] = (sig[i] + sig[i - 1]) % mod;
	}
}
inline ll S(ll x, ll y) {
	return (x + y) * (y - x + 1) % mod * inv2 % mod;
}
inline ll G(ll x) {
	return x * (x + 1) % mod * inv6 % mod * (x << 1 | 1) % mod;
}
inline ll get_mu(ll x) {
	if (x <= M) return mu[x];
	if (ans_mu[x]) return ans_mu[x];
	ll ans = 1;
	for (ll l = 2, r; l <= x; l = r + 1) {
		r = x / (x / l);
		(ans += mod - (r - l + 1) * get_mu(x / l) % mod) %= mod;
	}
	return ans_mu[x] = ans;
}
inline ll get_mud(ll x) {
	if (x <= M) return mud[x];
	if (ans_mud[x]) return ans_mud[x];
	ll ans = 1 * 1;
	for (ll l = 2, r; l <= x; l = r + 1) {
		r = x / (x / l);
		(ans += mod - S(l, r) * get_mud(x / l) % mod) %= mod;
	}
	return ans_mud[x] = ans;
}
inline ll get_sig(ll x) {
	if (x <= M) return sig[x];
	if (ans_sig[x]) return ans_sig[x];
	ll ans = S(1, x);
	for (ll l = 2, r; l <= x; l = r + 1) {
		r = x / (x / l);
		(ans += mod - (get_mu(r) - get_mu(l - 1) + mod) * get_sig(x / l) % mod) %= mod;
	}
	return ans_sig[x] = ans;
}
inline ll get_ans(ll x) {
	ll ans = 0, tmp;
	for (int l = 1, r; l <= x; l = r + 1) {
		r = x / (x / l);
		tmp = get_sig(x / l);
		(ans += (get_mud(r) - get_mud(l - 1) + mod) % mod * tmp % mod * tmp % mod) %= mod;
	}
	return ans;
}
int main() {
	init(); n = read();
	printf("%lld\n", get_ans(n));
	return 0;
}
posted @ 2021-12-18 16:32  Illusory_dimes  阅读(63)  评论(0编辑  收藏  举报