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

解决思路:

  • 设计一个calc函数,统计1-x中1~9的数字出现次数。 用2位数打个比方,统计2位数中1出现的次数 10-19枚举for(int i=10; i<=19; ++i) 那么 x/i,就是1-x中含有因数i的个数。通过观察可以发现这个循环在x的位数偏小时计算较快。
  • 那么这个数位数较多时?我们可以考虑分块的思想,与上方位数较少时进行互补。我们可以发现 用x/10得到_ed,通过循环变量j枚举[1,_ed];通过[10,19]中每个数乘以j得到的数在x范围内的个数。

    https://ac.nowcoder.com/acm/contest/view-submission?submissionId=43366330&returnHomeType=1&uid=919749006

代码如下

#include <bits/stdc++.h>
using namespace std;
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
typedef long long ll;

inline int read() {
    int s = 0, w = 1; char ch = getchar();
    while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); }
    while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
    return s * w;
}

ll l, r, a[20], b[20];
vector<ll> p; //p中存放 1 10 100 1000...

void init() {
    ll temp = 1;
    for (int i = 0; i < 10; ++i) {
        p.push_back(temp);
        temp = (temp << 3) + (temp << 1); // *8+*2=*10
    }
}

void calc(ll x, ll* temp) {
    for (int num = 1; num <= 9; ++num) { //枚举首数字
        for (auto i : p) { //基于范围的for遍历容器p
            if (i < 100000) {
                // Plan A,位数较少时
                ll st = i * num;
                ll ed = st + i - 1;
                if (st > x)    break;
                for (ll j = st; j <= ed; ++j)
                    temp[num] += x / j;
            }
            else {
                // Plan B
                ll st = i * num;
                ll ed = st + i - 1;
                if (st > x)    break;
                ll _ed = x / st;
                for (int j = 1; j <= _ed; ++j) {
                    ll a = x - st * j;
                    temp[num] += min(a / j + 1, ed - st + 1);//j相当于间距
                }
            }
        }
    }
}

int main() {
    init();

    l = read(), r = read();

    calc(l - 1, a);
    calc(r, b);

    for (int i = 1; i <= 9; ++i)
        printf("%lld\n", b[i] - a[i]);

    return 0;
}