【数位统计dp】计数问题

这里介绍计数问题的初级写法,较为通俗易懂。首先来看看问题:
原题 acwing 计数问题

给定两个整数 a 和 b,求 a 和 b 之间的所有数字中 0∼9 的出现次数。
例如,a=1024,b=1032,则 a 和 b 之间共有 9 个数如下:
1024 1025 1026 1027 1028 1029 1030 1031 1032
其中 0 出现 10 次,1 出现 10 次,2 出现 7 次,3 出现 3 次等等…
输入格式
输入包含多组测试数据。
每组测试数据占一行,包含两个整数 a 和 b。
当读入一行为 0 0 时,表示输入终止,且该行不作处理。
输出格式
每组数据输出一个结果,每个结果占一行。
每个结果包含十个用空格隔开的数字,第一个数字表示 0 出现的次数,第二个数字表示 1 出现的次数,以此类推。
数据范围
0<a,b<100000000
输入样例

1 10
44 497
346 542
1199 1748
1496 1403
1004 503
1714 190
1317 854
1976 494
1001 1960
0 0

输出样例

1 2 1 1 1 1 1 1 1 1
85 185 185 185 190 96 96 96 95 93
40 40 40 93 136 82 40 40 40 40
115 666 215 215 214 205 205 154 105 106
16 113 19 20 114 20 20 19 19 16
107 105 100 101 101 197 200 200 200 200
413 1133 503 503 503 502 502 417 402 412
196 512 186 104 87 93 97 97 142 196
398 1375 398 398 405 499 499 495 488 471
294 1256 296 296 296 296 287 286 286 247

大致思想就是:假设要统计数字 x x x出现的次数,那么就统计一下a~b之间每一位上 x x x出现的次数,也就是个位上 x x x出现的次数、十位上 x x x出现的次数、百位上 x x x出现的次数……把这些加起来,就是 x x x出现的总次数了。
同时我们发现统计a~b之间的数并不是很好统计,但是统计1~a之间的数很好统计。那么只要变通一下,想要统计ab之间的,只要先统计一个1~b之间的,再统计一个1~a-1之间的,然后用前者减去后者,就能得到a~b之间的了。(有些类似于前缀和思想)

剩下要解决的问题就是如何统计1~a之间某个数出现的次数了。这里采用一个分类讨论的思想,一点一点找规律。

首先因为零比较特殊,要统计的数字分为零和非零

一、要统计的数字非零

以七位数举例,假设数字是 a b c d e f g abcdefg abcdefg,现在要统计 x x x d d d位置上(也就是从右起第四位)出现的次数,只要找到这样的数有多少个即可。现在把1~ a b c d e f g abcdefg abcdefg所有的数分成两大类:

  1. 一个数左起前三位在000~ a b c abc abc之间
  2. 一个数左起前三位就是 a b c abc abc

1、一个数左起前三位在0~ a b c − 1 abc-1 abc1之间

这时候无论剩下的怎么取,都一定是在范围内的。那么让第四位是x,最后三位所有组合就是000~ 999 999 999之间所有数,也就是一千个。前面三个数可以取000~ a b c − 1 abc-1 abc1,也就是 a b c abc abc个。那么这样的数总个数就是 1000 × a b c 1000×abc 1000×abc

2、一个数左起前三位就是 a b c abc abc

这里又可以分成三种小情况

  1. d < x d<x d<x
  2. d = x d=x d=x
  3. d > x d>x d>x
2.1、 d < x d<x d<x

这时候前三位是 a b c abc abc,但是如果 d d d位置取 x x x,那就超出范围了,所以这时候一个数都没有;

2.2、 d = x d=x d=x

前三位是 a b c abc abc,把 d d d位置设置成 x x x,也就是 d d d,所以问题转化为了前四位是 a b c d abcd abcd的范围内的数有几个。即是后三位取000~ d e f def def,也就是 d e f + 1 def+1 def+1个符合的数。

2.3、 d > x d>x d>x

d d d位置设置成 x x x,随便取一定是在范围内的。所以后面三位可以取000~999,一共1000个。

二、要统计的数字是零

这里只要注意一个前导0的情况即可。举个例子:假设要统计0在 d d d位置上的数有几个,那么 a b c abc abc就不能是000,因为这时候符合的数就变成了 0000 d e f 0000def 0000def,但是这时候的0是不能算入计数的。
这么说来,要统计0时只对一个数左起前三位在000~ a b c − 1 abc-1 abc1之间有影响,这时候就不能是000~ a b c abc abc了,要是001~ a b c − 1 abc-1 abc1(保证前面不全是0),所以这样的数就是 1000 × ( a b c − 1 ) 1000×(abc-1) 1000×(abc1)个。


有了以上推导,现在推广至所有情况:
在这里插入图片描述
另外要注意的是,如果要统计的是0的话,就不要迭代最高位了。由此可以给出题解:

#include <iostream>
#include <vector>

using namespace std;

const int N = 10;

//截取某一段数
int get(vector<int> num, int l, int r)
{
    int res = 0;
    for (int i = l; i >= r; i -- ) res = res * 10 + num[i];
    return res;
}

//计算10的幂次
int power10(int x)
{
    int res = 1;
    while (x -- ) res *= 10;
    return res;
}

int count(int n, int x)
{
    if (!n) return 0;
    
    //先把每一位上的数存进来
    vector<int> num;
    while (n)
    {
        num.push_back(n % 10);
        n /= 10;
    }
    n = num.size();
    
    int res = 0;
    //遍历每一位,分类讨论
    for (int i = n - 1 - !x; i >= 0; i -- )
    {
        res += get(num, n - 1, i + 1) * power10(i);
        if (!x) res -= power10(i);
        
        if (num[i] == x) res += get(num, i - 1, 0) + 1;
        else if (num[i] > x) res += power10(i);
    }
    
    return res;
}

int main()
{
    int a, b;
    while (cin >> a >> b, a || b)
    {
        if (a > b) swap(a, b);
        
        for (int i = 0; i <= 9; i ++ )
            cout << count(b, i) - count(a - 1, i) << ' ';
        cout << endl;
    }
    
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值