1 条题解
-
0
自动搬运
来自洛谷,原作者为

syksykCCC
相信并抓住那些源于热爱,忠于自我的每一个可能性搬运于
2025-08-24 22:22:10,当前版本为作者最后更新于2020-06-03 00:37:54,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
zrm 哥哥自己不写 sol 做 ppt 差评 /kel
好,那么官方题解只能我来写了 /kk
十进制有限小数
什么样子的分数可以表示为十进制有限小数?
约分后,分母只含 两种因子的分数可以。
感性证明:
写成十进制有限小数的充要条件显然是分数可以通分为 的形式,也就是对于一个整数左移小数点。
而形如 的数必然 ,所以,分母只含 两个因子即可。
子任务 1:
做法 1
显然,枚举 ,枚举 ,暴力约分,判断 约分后是不是只含 两种因子是可行的。
枚举的时间复杂度为 ,约分的时间复杂度为 ,判断分母的时间复杂度也为 ,后面两者并列,所以,时间复杂度为 。
至此,你还没有当年的小 Z 厉害(
#include <bits/stdc++.h> using namespace std; int main() { int n, ans = 0; cin >> n; for(int i = 1; i <= n; i++) { for(int j = 1; j <= n; j++) { int p = i, q = j; int g = __gcd(p, q); p /= g; q /= g; while(q % 2 == 0) q /= 2; while(q % 5 == 0) q /= 5; if(q == 1) ans++; } } cout << ans << endl; return 0; }做法 2
把分数分解一下,可以写为以下的形式:
本身只含 两种因子, 本身不含 两种因子(也就是把分母需要约分的部分拆出来), 为任意数,显然,这样的分数都是合法的,而且任何合法的分数都是这种形式。
那么,依次在 范围枚举 (合法的 的表可以预处理)在 范围枚举合法的 ,在 的范围内枚举 ,每枚举到一次就把答案加 ,时间复杂度 。
#include <bits/stdc++.h> #define int long long using namespace std; int n, x[100000], cnt, ans; signed main() { cin >> n; for(int i = 1; i <= n; i *= 2) for(int j = i; j <= n; j *= 5) x[++cnt] = j; sort(x + 1, x + cnt + 1); for(int i = 1; i <= cnt; i++) { int a = x[i]; for(int b = 1; a * b <= n; b++) { if(b % 2 == 0 || b % 5 == 0) continue; for(int c = 1; b * c <= n; c++) { ans++; } } } cout << ans << endl; return 0; }子任务 2:
被极短的 切了,长见识了 qwq
做法 1
观察上一个做法,发现不知道最后一重循环在干啥 /发抖
也就是说,枚举完一组 和 后,它对答案的贡献就是分子所能取的数量,即 ,直接加上这个贡献就好了。
那么省掉了一重循环,因为分母的枚举是 个不重不漏,时间复杂度 。
#include <bits/stdc++.h> #define int long long using namespace std; int n, x[100000], cnt, ans; signed main() { cin >> n; for(int i = 1; i <= n; i *= 2) for(int j = i; j <= n; j *= 5) x[++cnt] = j; sort(x + 1, x + cnt + 1); for(int i = 1; i <= cnt; i++) { int a = x[i]; for(int b = 1; a * b <= n; b++) { if(b % 2 == 0 || b % 5 == 0) continue; ans += n / b; } } cout << ans << endl; return 0; }做法 2
想要继续优化,发现很难了,回到 的做法,能不能通过转换顺序来达到优化时间的目的呢?
尝试先枚举 ,那么, 的取值依然是 的任何数,而 的取值就变为了 中任何只含 两种因子的数,发现, 的取值的个数随着 的增大而单调减小,所以可以在总共 的时间中计算,也就是,对于每个 ,记合法的 的个数为 ,则它对答案的贡献为 。
时间复杂度依然是 。
#include <bits/stdc++.h> #define int long long using namespace std; int n, x[100000], cnt, ans; signed main() { cin >> n; for(int i = 1; i <= n; i *= 2) for(int j = i; j <= n; j *= 5) x[++cnt] = j; sort(x + 1, x + cnt + 1); int a = cnt; for(int b = 1; b <= n; b++) { if(b % 2 == 0 || b % 5 == 0) continue; while(a && x[a] > n / b) a--; ans += a * (n / b); } cout << ans << endl; return 0; }子任务 3:
不妨把 写的更直接一点,换成 ,表示 中只含 两个因子的数的个数。前面那个式子等价于:
$$f\left(\left\lfloor\frac{n}{c} \right\rfloor\right)\times\left\lfloor\frac{n}{c} \right\rfloor $$发现,这个是一种经典的整除分块形式,直接整除分块,可以把时间复杂度加速到 。
真的这么简单么?
别忘了, 的取值并不是 当中的每一个数,而是不含 因子的数。
之前枚举的是 ,所以可以直接判断 和 是不是为 ,但现在我们要整段的处理 ,怎么办呢?
先忽略这个限制,把 的取值范围当作 当中的每一个数处理。比如 时贡献相同,就可以通过容斥计算出 中实际的合法的 的个数。
$$e= g([l, r], 1)-g([l, r], 2)-g([l, r], 5)+g([l, r], 10) $$表示 中 的倍数的个数,可以类似前缀和得到。(即 ,左端点为 显然除一除就好了)
那么,这一段的贡献就是 $e\times f\left(\left\lfloor\frac{n}{c} \right\rfloor\right)\times\left\lfloor\frac{n}{c} \right\rfloor$,时间复杂度 。
#include <bits/stdc++.h> #define int long long using namespace std; int n, x[100000], cnt, ans; signed main() { cin >> n; for(int i = 1; i <= n; i *= 2) for(int j = i; j <= n; j *= 5) x[++cnt] = j; sort(x + 1, x + cnt + 1); int a = cnt; for(int l = 1, r; l <= n; l = r + 1) { int t = n / l; r = t == 0 ? n : n / t; while(a && x[a] > t) a--; int valid = r - l + 1; valid -= r / 2 - (l - 1) / 2; valid -= r / 5 - (l - 1) / 5; valid += r / 10 - (l - 1) / 10; ans += valid * a * t; } cout << ans << endl; return 0; }水一句:比神花还短((光速逃
- 1
信息
- ID
- 5229
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 5
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者