Acwing 数位统计DP

Acwing 338.计数问题

在这里插入图片描述
输入样例:

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

实现思路:

  • 定义函数:count(n,x),其表示在1n中,x出现的次数(x是0-9),那么,可以用类似前缀和的思想,来求解ab中,x出现的次数:count(b,x) - count(a-1,x)

  • x = 1为例,如何计算count(n, 1):分情况讨论。比如n是个7位的数字 abcdefg我们可以分别求出1在每一位上出现的次数,然后做一个累加即可。比如求1在第4位上出现的次数,求有多少个形如xxx1yyy的数在1abcdefg之间。分两种大情况:

    • xxx = 000 ~ abc - 1, 中间d=1,yyy = 000 ~ 999,一共有abc * 1000(即左右两边数的大小相乘)种选法
    • 若xxx = abc
      • d < 1,abc1yyy > adc0efg,超出n的范围,0种
      • d = 1,yyy = 000 ~ efg,efg + 1种
      • d > 1,yyy = 000 ~ 999,1000种
  • 把上面全部的情况,累加起来,就是1出现在第四位的次数。
    类似的,可以求解出1在任意一个位置上出现的次数,累加起来,就求出了1在每一位上出现的此时,即求解出了count(n,1)

  • 注意:当x=0,不能有前导0,所以当x=0时,形如xxx0yyy,前面的xxx是从001(不能从00开始,左边选法相比不是0的情况就少了一次)999

具体实现代码(详解版):

#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

//求n的位数
int get(int n){
    int res = 0;
    while(n) res ++,n /= 10;
    return res;
}

//求1到n种i出现的次数

int count(int n ,int i){
    int res = 0,dgt = get(n);
    
    //遍历每一位 求出每一位出现i的次数
    for(int j = 1 ; j <=dgt ; j ++){
         /*利用位运算
        得到当前遍历位次(第j位)的数的大小p:10^(j右边的位数即dgt-j)
        得到第j位左边的数大小l
        得到第j位右边的数大小r
        得到第j位上的数dj
        */
        int p = pow(10,dgt - j), l = n / p /10,r = n % p,dj = n / p % 10;
        
        //然后分情况讨论 用i所在的位置划分出左边、右边
        /* 一、xxx..i..yyy的选法 左边取小于n中实际的左边的数l
        1)当i不为0时:左边000...~xxx...-1,右边...yyy就任意取了,
        取法:左边的数大小l*10^右边位数=l*p
        2)当i=0时,排除前导0的情况:左边000..1~xxx...-1,右边和上面一样,取法:(l-1)*p
        */
        if(i) res += l * p;
        else res += (l - 1) * p;
        
         /* 二、左边固定为n的左边的数l
         1)、i > dj时 0种选法
         2)、i == dj时 yyy : 0...0 ~ r 即 r + 1 种选法
         3)、i < dj时 yyy : 0...0 ~ 9...9 即 10^(右边的数的位数) == p 种选法
        */
        if(i == dj) res += r + 1;
        if(i < dj) res += p;
    }
    
    return res;
}

int main(){
    int a,b;
    while(cin >> a >> b && a){
        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;
}

DP的难点就是分类讨论,可以具体拿个例子试一下。唉,多积累经验把~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值