1 条题解
-
0
「数码」题解(基于代码分析)
题意简化
对于 到 之间的每个数 ,列出 的所有约数。
对每个约数,取其最高位数字(1-9)。
统计每个数字 1-9 出现的总次数。。
核心思想:贡献转换
1. 问题转化
不是枚举每个 再枚举其约数,而是考虑每个可能的约数 对多少个数 有贡献。
对于一个固定的 ,它是哪些 的约数?
- 是 的倍数
- 在 范围内
所以 的贡献次数 = 中 的倍数的个数 = $\lfloor \frac{r}{d} \rfloor - \lfloor \frac{l-1}{d} \rfloor$
2. 只关心最高位
我们只关心 的最高位数字。
最高位为 的 形如:即所有最高位为 的数。
代码算法解析
1.
solve(a, b)函数计算:在 到 中,以数字 开头的数,作为约数的总贡献次数。
ll solve(ll a, ll b) { ll res = 0, beg, end; for (ll i = 1; i <= a / b; i *= 10) { beg = b * i; // 区间开头 end = min(a, beg + i - 1); // 区间结尾 for (int j = beg; j <= end; j = k + 1) { k = min(a / (a / j), end); res += (k - j + 1) * (a / j); } } return res; }解释:
i表示位数:beg = b * i:最高位为 的 位数的最小值- → 3
- → 30
- → 300
end = min(a, beg + i - 1):该位数的最大值- 比如 300-399(如果 足够大)
这样枚举了所有最高位为 的数。
2. 数论分块优化
对于区间 ,直接枚举每个 计算 会超时。
使用数论分块:
- 的值在 的一个区间内不变
- 对于 , 相等
- 可以一次性计算这个区间的贡献:
代码中:
for (int j = beg; j <= end; j = k + 1) { k = min(a / (a / j), end); res += (k - j + 1) * (a / j); }j是区间起点k是当前值 能保持不变的区间终点min(a / (a / j), end)确保不超过当前位数区间的上限
3. 最终计算
对于每个数字 :
cout << solve(r, i) - solve(l-1, i) << endl;solve(r, i): 到 中,最高位为 的约数的总贡献solve(l-1, i): 到 中的贡献- 差值就是 区间的贡献
算法步骤
- 枚举最高位数字
- 枚举位数 直到
- 对于每个 区间:
- 使用数论分块计算
- 求区间差: 的答案 = 的答案 - 的答案
复杂度分析
- 对于每个 ,位数 有 个
- 每个区间用数论分块计算,复杂度
- 总复杂度:,可过
关键优化
1. 贡献转换
从“每个数的约数”转为“每个约数的倍数”,避免平方复杂度。
2. 按最高位分组
将约数按最高位分组,分别统计。
3. 数论分块
高效计算 ,避免线性枚举。
样例解释
输入:
1 4约数列表:
- 1: {1} → 最高位1
- 2: {1,2} → 最高位1,2
- 3: {1,3} → 最高位1,3
- 4: {1,2,4} → 最高位1,2,4
统计:
- 1出现4次
- 2出现2次
- 3出现1次
- 4出现1次
- 5-9出现0次
输出:
4 2 1 1 0 0 0 0 0代码特点
- 使用
long long防止溢出 - 数论分块模板
- 简洁的区间枚举
- 差值计算避免重复
- 1
信息
- ID
- 6017
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者