P6583 回首过去 题解
Published:
P6583 回首过去 题解
好题。
更新(2023-12-05):把代码和 $\LaTeX$ 改得更好看了。
题意简述
给定正整数 $n$,求出有序整数对 $(x, y)$ 的个数,满足 $1 \le x,y \le n$ 且 $\dfrac x y$ 可以表示为十进制有限小数。
$1 \le n \le 10^{12}$。
前置知识
- 数论分块
解法
Subtask 1 $O(n^2 \log n)$
根据小学奥数知识可知,一个分数在十进制下为有限小数,当且仅当约分后的分母的质因数分解只有 $2$ 和 $5$。
暴力验证每一个范围内的分数。
Subtask 2 $O(n \log n)$
根据上文的结论,设 $y = 2^a 5^b d, x = \lambda d$。其中 $d$ 与 $2$ 和 $5$ 互质。
首先需要知道这样一件事:
记 $f(x)$ 为 $[1,x]$ 中有几个数是 $2^a 5^b$ 的形式,其中 $a,b$ 可以等于 $0$。
我们可以在 $O(\log n)$ 的时间内快速计算 $f(n)$:
\[f(n) = \sum\limits_{i=0}^{\log_2 n} \left(\left\lfloor\log_5 \dfrac n {2^i}\right\rfloor+1\right)\]在代码中 $\log_5 x$ 可以用 $\dfrac {\ln x}{\ln 5}$ 来计算。
ll f(ll x) {
ll ret = 0;
for (ll i = 0; (1ll << i) <= x; i++)
ret += ll(log(x >> i) / log(5)) + 1;
return ret;
}
记 $val = \left\lfloor \dfrac n d \right\rfloor$。
所以我们可以枚举 $d$,那么 $y$ 的个数即为 $f(val)$,$x$ 的个数即为 $val$。
即答案为:
\[\sum\limits_{d=1}^n [2 \nmid d][5 \nmid d] \times f(val) \times val\] ll ans = 0;
for (ll d = 1; d <= n; d++) {
if (d % 2 == 0 || d % 5 == 0)
continue;
ll val = n / d;
ans += val * f(val);
}
cout << ans << '\n';
Subtask 3 $O(\sqrt n \log n)$
注意到 $val$ 的形式长得很像数论分块,考虑使用数论分块将其加速到根号级别。
在 $val$ 为定值的区间内,$f(val) \times val$ 为一定值,但是 $[2 \nmid d][5 \nmid d]$ 会变。
也就是说,我们需要知道在一个区间 $[l,r]$ 内有几个数满足 $2 \nmid d$ 且 $5 \nmid d$。
考虑前缀和,设 $g(x)$ 为 $[1,x]$ 内有几个数满足 $2 \nmid d$ 且 $5 \nmid d$。
根据容斥,有 $g(x) = x - \left\lfloor \dfrac x 2 \right\rfloor - \left\lfloor \dfrac x 5 \right\rfloor + \left\lfloor \dfrac x {10} \right\rfloor$。
区间 $[l,r]$ 内满足 $2 \nmid d$ 且 $5 \nmid d$ 的数的个数则为 $g(r) - g(l-1)$。
此时可以使用数论分块了。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll f(ll x) {
ll ret = 0;
for (ll i = 0; (1ll << i) <= x; i++)
ret += ll(log(x >> i) / log(5)) + 1;
return ret;
}
ll g(ll x) {
return x - x / 2 - x / 5 + x / 10;
}
int main() { ios::sync_with_stdio(0); cin.tie(0);
ll n; cin >> n;
ll ans = 0;
for (ll l = 1, r; l <= n; l = r + 1) {
ll val = n / l;
r = n / val;
ans += (g(r) - g(l-1)) * val * f(val);
}
cout << ans << '\n';
return 0;
}