美团资格赛题解:5


给定两个整数 l 和 r ,对于所有满足1 ≤ l ≤ x ≤ r ≤ 10^9 的 x ,把 x 的所有约数全部写下来。对于每个写下来的数,只保留最高位的那个数码。求1~9每个数码出现的次数。

输入描述:

一行,两个整数 l 和 r (1 ≤ l ≤ r ≤ 10^9)。

输出描述:

输出9行。
第 i 行,输出数码 i 出现的次数。

输入例子:

1 4

输出例子:

4
2
1
1
0
0
0
0
0

思路:

首先,我们只需要分别计算1-l和1-r的数码出现次数,两个数组相减即可得到答案。因此下面仅仅计算1~n的数码即可。

其次,我们知道,整数m在1~n所有数的因数中出现的次数为$\lfloor \frac{n}{m}\rfloor​$,因此一种很自然的思路是枚举m,可是由于r最大可能为$10^9​$,因此线性时间复杂度肯定是不允许的。

换个思路,对于m比较大时,$|\lfloor \frac{n}{m}\rfloor - \lfloor \frac{n}{m+1}\rfloor| <= 1$,也就是说变化应该是连续的,由此我们可以求出发生变化的位置,而不是枚举每一个数。代码如下:

vector<long long> sum(long long r) {
    vector<long long> ans(10, 0);
    for(int i = 1; i <= 9; i++) {
        long long n = i;
        for(int j = 1; j <= 10; j++) {
            if(n > r) break;
            long long maxN = min(r, (i + 1) * (n / i) - 1);
          	// n 较大时更加变化的位置求解
            if(n >= 100000) {
                long long left = r / n, right = r / maxN;
                for(long long k = left; k >= right; k--) {
                    ans[i] += k * (min(maxN, r / k) - max(n - 1, r / (k + 1)));
                }
            }
            // n 较小时枚举
            else {
                for(long long k = n; k <= maxN; k++) ans[i] += r / k;
            }
            n = n * 10;
        }
    }
    return ans;
}

int main() {
    int l, r;
    cin >> l >> r;
    vector<long long> L = sum(l - 1), R = sum(r);
    for(int i = 1; i <= 9; i++) cout << R[i] - L[i] << endl;
    return 0;
}