「51nod1220」 约数之和
复盘 \(Bindir0\) 讲的数论清真题,写一篇题解来祸害自己。
Description
给定 \(n\) ,求
结果对 \(10 ^ 9 + 7\) 取模。
\(2\leq n \leq 10 ^ 9\)
Analysis
无脑转化式子+打表+无脑乱卷函数=不用推式子!
Solution
按照一贯的套路,把 \(\sigma\) 换一种写法,但是注意 \(\sigma\) 注意有可能会算重,所以要限制枚举,要求两个因数相对独立:
写法不太好看,改成:
然后是另外一个常见的套路,就是交换枚举顺序,注意下面的 \(i \cdot p\) 就是对应上面的 \(i\) ,要清楚枚举东西的含义:
稍稍化简一下,把 \(p\) 消掉,然后后面两坨可以直接写成公式的样子,稍微换一下位置:
感觉最后面那个限制让我们很难放开手脚去把 \(p\) 和 \(q\) 分开算,据说是有两种做法,但因为本人能力有限,就先说一下莫反的做法。
以限制条件作为基础,可以很自然地写出两个函数形如:
然后很好写莫反的式子:
因为我们最终要求的答案就是 \(f(1)\) ,所以就把函数带进去,同时如我们要让最后的式子能通过什么递推算,还是把后面的那两坨和 \(d\) 没有关系的式子硬提出来一个 \(d\) ,然后注意保持枚举的东西的一致性:
最后面的 \(d\) 可以甩到前面去,但就不单独列出来了。然后来看 \(p\) 这一层的式子大概是个什么鬼。
其实这样已经化不下去了,那就继续用老套路,调换枚举顺序,算 \(i\) 出现的次数:
这样还是不行,因为 \(p\) 没有单独分出来,但是发现下取整对应大于,放缩一下可以得到:
然后你发现这和后面那坨不谋而合,所以快乐合并:
又不快乐了,因为后面那坨压根和常用函数没关系,虽然没有约束条件,可以放开手脚了,但是卷积卷不起来呀。。
于是又回头来看刚刚推得的式子,可以考虑考虑他的含义,等于是说这个范围内包含因子 \(i\) 的数一共有 \(\lfloor n / i \rfloor\) 个,不重,因为每个因子都枚举到了,不漏,那合到一起不就刚好是 \(\sum\sigma\) 吗:
终于该筛法上场了,但是因为本人水平有限,只会杜教筛的做法,min25筛可能要等到千年以后了。。
所以这样来看现在我们就要卷两个东西,一个是 \(\mu \cdot id\) ,另一个是 \(\sigma\) 。
前者的话我们直接选一个 \(id\) 卷上去,正好能把原式里面的 \(id\) 卷掉:
嗯,好极了!
后者的话乍一看并不知道有什么常用公式能套上去,但是,我们知道根据定义,有这样几个东西:
目标乍现!!
所以 \(\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;
}