页码问题

题目来源

这道题来自于牛客

https://ac.nowcoder.com/acm/problem/15327

下面给出该题的题面

原题题面

小明前几天看书看累了,脑海中突然闪过,这书的页码也很可爱啊。一本书的页码从自然数1按自然顺序编码到n.每个页码不会含有多余的前导数字0.例如,第6页用数字6表示,而不是06、006表示。下面问题来了,你能帮忙小明解决以下问题:给定总页码n,计算出书的全部页码中分别用到的多少次数字0,1,2,…,9.

输入格式

输入书的总页码一个整数n。

输出格式

输出总共10行。在第k行输出页码中用到数字k-1的次数。(k=1,2,…,10)

题目解释

暴力

首先,我是直接暴力解法,把每个数的每一位得出来的数字依次相加,设一个数组来记录0到9的数字个数,然后牛客说我超时了,用dev的时候发现在1e9的时候甚至要27s。想了很久没想明白,之后我借鉴了一个大佬的想法,直接算每一个数字的个数,
通过一些规律来计算。

优化

我们先设置一个数,以11459为例

先从1开始,从个位开始数,1,2,3,4,5,6,7,8,9,10每十位就有一个数的个位上是1,那么11459/10=1145……9,一共有1145个10,就有1145个1。由于还余了9,注意9大于1,那么还有11450,11451,11452…11459,这九个数没算,还少了一个1,我们就还要加一个1,所以个位上一共有1146个1;

现在从十位开始,11,12,13,14,15,16,17,18,19,20,…,99每百位就有十个数的十位上是1,那么11459/100=114…..59,一共有114个100,就有114*10个1。由于还余了59,注意5大于1,那么还有11410,11411,…,11419没算,还少了十个1,我们就还要加10个1,所以十位上一共有1150个1;

从百位开始,也遵循上面的思想,11459/1000=11……459,4大于1,所以百位上有11*100+100个1,即1200个1;

从千位开始,对于1来说就有些特别了,因为11459/10000=1……1459,所以有1*1000个1,因为1等于1,所以还有11000,11001,11002…,11459上的千位上的1没有数,从0开始到459有460个数,那就要再加460,即1460个1;

万位也是如此,11459/100000=0……11459,所以有0*10000个1,1等于1,就有10000,10001,10002…,11459上的万位上的1没有数,从0到1459有1460个数,那就要再加1460个1;最后一共有1146+1150+1200+1460+1460=6416个1;

同理2,3,4,5…,9也是这么算

例子

对于6为例

个位	11459/10=1145...9>6 			1145 * 1+1;
十位	11459/100=114...59<60 			114 * 10;
百位	11459/1000=11...459<600 		11 * 100;
千位	11459/10000=1...1459<6000 		1 * 1000;
万位	11459/100000=0...11459<60000 	      0 * 10000;
一共	1146+1140+1100+1000+0=4386个6;

再来考虑特殊的0,还是以11459为例

从个位开始,11459/10=1145…9>0 ,但此时不能单纯的用1145 * 1+1了,因为11450是被计算到那1145个0里面了
(你看1,2,3,4,5,6,7,8,9,10,在出现0的时候进位了,那么这个0在11459/10=1145时就被计算到了)

所以个位上有1145  * 1个0;
十位,		11459/100=114...59>10 				114 * 10;
百位,		11459/1000=11...459>100 			11 * 100;
千位,		11459/10000=1...1459>1000 			1 * 1000;
万位,		11459/100000=0...11459>10000 		      0 * 10000;
一共有		1145+1140+1100+1000+0=4385个0;

代码

代码1(简单循环找规律)

//牛客过不了
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
typedef long long ll;
ll sum1(ll n, ll i)
{
  ll sum = 0, m, s = 1, panduan;
  ll k = n;
  if (i == 0) {
    while (k > 0) {
      m = k / 10;
      sum += m * s;
      s *= 10;
          k /= 10;
       }
  }
  else {
    while (k > 0) {
      m = k / 10;
      sum += m * s;
      panduan = k % 10;
      if (panduan > i) {
      	sum += s;
    	}
    	if (panduan == i) {
      	sum += n - k * s + 1;
    	}
    	s *= 10;
    	}
  }
  return sum;
}
int main()
{
  ll n, i, m;
  cin >> n;
  for (i = 0; i <= 9; i++) {
    m = sum1(n,i);
    cout << m << endl;
  }
  return 0;
}

代码2(数位dp)

//牛客还是过不了
#include <bits/stdc++.h>
using namespace std;
const int N = 20;
typedef long long ll;
ll l, r, dp[N], sum[N], mi[N];
ll ans1[N], ans2[N];
int a[N];

void solve(ll n, ll *ans) {
  ll tmp = n;
  int len = 0;
  while (n) a[++len] = n % 10, n /= 10;
  for (int i = len; i >= 1; --i) {
    for (int j = 0; j < 10; j++) ans[j] += dp[i - 1] * a[i];
    for (int j = 0; j < a[i]; j++) ans[j] += mi[i - 1];
    tmp -= mi[i - 1] * a[i], ans[a[i]] += tmp + 1;
    ans[0] -= mi[i - 1];
  }
}

int main() {
  scanf("%lld%lld", &l, &r);
  mi[0] = 1ll;
  for (int i = 1; i <= 13; ++i) {
    dp[i] = dp[i - 1] * 10 + mi[i - 1];
    mi[i] = 10ll * mi[i - 1];
  }
  solve(r, ans1), solve(l - 1, ans2);
  for (int i = 0; i < 10; ++i) printf("%lld\n", ans1[i] - ans2[i]);
  return 0;
}

代码3

//牛客依然过不了
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;

int get(vector<int> u, int l, int r) {
    int res = 0;
    for(int i = l;i >= r;i--) {
        res = res * 10 + u[i];
    }
    return res;
}

int get10(int x) {
    int res = 1;
    while(x -- ) {
        res *= 10;
    }
    return res;
}

int count(int n, int x) {
    if(!n) return 0;
    
    vector<int> u;
    int res = 0;
    while(n) {
        u.push_back(n % 10);
        n /= 10;
    }
    n = u.size();
    for(int i = n - 1 - !x;i >= 0;i--) {
        if(i < n - 1) {
            res += get(u, n - 1, i + 1) * get10(i);
            if(!x) res -= get10(i);
        }
        if(u[i] == x) res += get(u, i - 1, 0) + 1;
        else if(u[i] > x) res += get10(i);
    }
    return res;
}

int main()
{
    int a, b;
    while(cin >> a >> b, a) {
        if(a > b) swap(a, b);
        for(int i = 0;i < 10;i++) {
            cout << count(b, i) - count(a - 1, i) << ' ';
        }
        cout << endl;
    }
    return 0;
}

写在最后

这篇博客实际上是我在博客园里面写的,由于在里面写的实在是破烂不堪(指的是格式让人不想看),于是我把他移植到了自己的小博客里面,嘿嘿(●ˇ∀ˇ●)

附录

博客园链接:关于c语言中的经典页码问题 - 彼岸的约定 - 博客园 (cnblogs.com)