题目:
给定两个整数 l 和 r ,对于所有满足1 ≤ l ≤ x ≤ r ≤ 10^9 的 x ,把 x 的所有约数全部写下来。对于每个写下来的数,只保留最高位的那个数码。求1~9每个数码出现的次数。
做法:
可以拆分成2个区间处理。。
问题变成了:统计区间中每个数的因子的最高位数码的出现次数。
例如:区间。
1的因子:1
2的因子:1、2
3的因子:1、3
4的因子:1、2、4
5的因子:1、5
6的因子:1、2、3、6
1作为因子出现了6次,2、3作为因子出现了2次,4、5、6作为因子出现了1次。
1-9数码出现次数:6、2、2、1、1、1、0、0、0。(7-9没出现过)
我们发现:我们不关注每个数有什么因子,我们只关注每个数作为因子出现多少次。
而且我们知道:区间中作为因子出现的次数为。(这是因为的倍数在区间中有个)
如果我们一个一个枚举所有因子,算出出现次数,然后统计数量。复杂度的。无法接受。
所以我们不能一个一个统计因子的贡献,要考虑一段一段统计。
我们用整除分块来实现一段一段统计。
什么是整除分块呢?
回到上面那个例子:
因子 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
出现次数 | 6 | 2 | 2 | 1 | 1 | 1 |
我们发现有一些连续的因子出现次数是相同的,也就是属于同一段:、、。
经证明: 将区间分成了一段一段相同的区间,这个东西就是整除分块了。(证明不会。。)
用整除分块实际上就是为了帮我们将区间分成相同的段,分每一段统计贡献。
然后问题就转变成:区间统计次最高位数码的出现次数。
同样拆分区间,、处理。
找找规律,考虑数码:
1-9中出现了1次。
10-99中出现了10次。
100-999中出现了100次。
...依次类推。
对于某个,设R是个位数,最高位数码为。
则数码出现了次、出现了次(括号里一长串其实就是尾数而已)、出现了次。
举个栗子:这个区间统计数码
1、2、3数码作为最高位出现了次
4数码作为最高位出现了次
5、6、7、8、9数码作为最高位出现了
到这里我们就有了解题的全部信息了。
结合代码理解一下。
代码:
#include <bits/stdc++.h> #define IOS ios::sync_with_stdio(false), cin.tie(0) #define debug(a) cout << #a ": " << a << endl using namespace std; typedef long long ll; const int N = 20; ll a[N], ten[N], prefix[N]; void init(void){//预处理 ten[1] = 1; for (int i = 2; i <= 10; ++i) ten[i] = ten[i-1]*10; for (int i = 1; i <= 10; ++i) prefix[i] = prefix[i-1]+ten[i]; } void calc(int R, ll k){//[1,R]因子算k次贡献 if (R == 0) return; int len = 0, t = R, lim;//len为R数位,lim为R最高位 while (t){ len++, lim = t%10, t /= 10; } //根据规律算贡献。 for (int i = 1; i < lim; ++i) a[i] += k*prefix[len]; a[lim] += k*(prefix[len-1]+(R-lim*ten[len]+1)); for (int i = lim+1; i <= 9; ++i) a[i] += k*prefix[len-1]; } void solve(int R, int op){//统计[1,R]所有数的因子的最高位数码 //我们不关心每个数有哪些因子,只关心这个区间中,每个因子出现的次数 for (int i = 1, j; i <= R; i = j+1){//整除分块(枚举每个因子) j = R/(R/i);//因子[i,j]都出现了n/i次。 calc(j, 1ll*op*(R/i));//加上[1,j]贡献 calc(i-1, -1ll*op*(R/i));//减去[1,i-1]贡献 } } int main(void){ IOS; init(); int l, r; while (cin >> l >> r){ solve(r, 1), solve(l-1, -1); for (int i = 1; i <= 9; ++i) cout << a[i] << endl; } return 0; }