ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

AcWing 338. 计数问题

2021-10-25 14:02:47  阅读:244  来源: 互联网

标签:tmp 10 338 int res ll 大于 计数问题 AcWing


题目传送门

1、为什么不能用暴力解?

数据范围太大了,\(0<a,b<100000000\)
两个数字\(a-b\)之间,最多就有\(1^8\)个数字,每个\(10^8\)数,需要遍历每一位,就是一个数字需要遍历8次最多。
那就一次的时间复杂度最高就是:$10^8 * 8 $,而且有多组测试数据,不出意外会TLE。

2、按小学数奥问题分析

如计算 78501 中 "5" 出现的次数。 [答案:41502]
我们可以枚举“5”出现的位置,
1、如当“5”位于倒数第1位时,写成 xxxx5,由于5大于1(当前数位1小于目标值5),前面只能取0~7849(共7850个);

2、如当“5”位于倒数第2位时,写成xxx5x,由于5大于0(当前数位0小于目标值5),前面只能取0~784(共785个),后面无限制为10; 总计:7850个

3、如当“5”位于倒数第3位时,写成xx5xx,由于5等于5(当前数位5等于目标值5),前面取0~77乘以后面无限制的100,共7800个;加上,前面取78,后面取“01”;因为还有"00",所以还需要加上一个1 ,总计:7802个。

4、如当“5”位于倒数第4位时,写成 x5xxx,由于5小于8(当前数位8大于目标值5),前面可取0~7(共8个),后面无限制的1000. 总计:8000个

5、如当“5”位于倒数第5位时,写成 5xxxx,由于5小于7(当前数位7大于目标值5),后面无限制的10000. 总计:10000个
总之,枚举x出现的位置,按x与n在该位上的大小关系,分为大于、小于、等于三类讨论。

加在一起: 7850+7850+7802+8000+10000=41502

ll count_x(ll n, ll x) {
    ll res = 0, tmp = 1;
    ll tmp_n = n;                                                           //n的副本,因为下面是以n为运算的基础,一路除下去的,如果我们想要得到原始的n,就找不到了,这里给它照了一个像
    while (n) {                                                             //从后向前一位一位的讨论
        //代码中注释掉的两行,用于跟踪调试程序的分步运行情况
        //int t = res;
        if (n % 10 < x) res += n / 10 * tmp;                                //小于  n/10代表当前位数前面的数字值,比如785,tmp是指需要乘以10的几次方
        else if (n % 10 == x) res += n / 10 * tmp + (tmp_n % tmp + 1);      //等于  等于最麻烦,需要再次分情况讨论
        else res += (n / 10 + 1) * tmp;                                     //大于  这个加1,是因为是0~77,共78个
        n /= 10;                                                            //进入前一位
        tmp *= 10;                                                          //计算需要乘几个10的变量
        //cout << res - t << endl;
    }
    return res;
}

3、对于0这个东东比较讨厌,需要单独讨论一下

(1)因为0不能当首位
就是上面讨论中提到的什么07,07849 啊,都不能从0开始,那样的话,前面就成了存在前导0了。

(2)在每一位判断时,不存在比0小的数字。
只需要分成两类,不可能存在当前位小于0的情况,只讨论等于和大于。

ll count_0(ll n) {
    ll res = 0, tmp = 1;
    ll tmp_n = n;
    while (n) {
        //代码中注释掉的两行,用于跟踪调试程序的分步运行情况
        //int t = res;
        if (n % 10 == 0) res += (n / 10 - 1) * tmp + (tmp_n % tmp + 1);    //等于,剔除了大于0的情况
        else  res += (n / 10) * tmp;                                       //大于,剔除了大于0的情况

        n /= 10;                                                           //进入前一位
        tmp *= 10;                                                         //计算需要乘几个10的变量
        //cout << res - t << endl;
    }
    return res;
}

4、写一个最笨的计算办法,与用数学方法计算出来的结果进行对比,验证一下思路的正确性

//数位分离之原始版本[笨蛋版本,慢,但绝对准确,用于算法对比判断正确性]
ll stupidCount(int n, int x) {
    ll res = 0;
    for (int i = 1; i <= n; ++i) {
        int tmp = i;
        while (tmp) {
            int t = tmp % 10;
            if (t == x) res++;
            tmp /= 10;
        }
    }
    return res;
}

验证办法:
int n = 78501;
printf("%d ", stupidCount(n, 5));
printf("%d ", stupidCount(n, 0));

5、要的是区间段,我们算的是从1到n,怎么办呢?

现在的题目要求我们求出\([a,b]\)之间 \([0-9]\)出现的次数,这不太好求,如果我们有一个函数,可以计算出\([1-n]\)之间\(x\)出现的次数,就可以使用类似于前缀和的思想,计算出来,比如:
\(f[a,b]=count(b,x)-count(a-1,x)\)

6、测试对拍的程序

 int n = 78501;
printf("%lld ", count_0(n));
for (int i = 1; i <= 9; i++) printf("%lld ", count_x(n, i));

printf("\n");

for (int i = 0; i <= 9; i++)  printf("%lld ", stupidCount(n, i));

//printf("%lld ", count_x(n,5));
//printf("%d ", stupidCount(n, 5));

7、完整C++ 代码

#include <iostream>

using namespace std;
typedef long long ll;

ll count_x(ll n, ll x) {
    ll res = 0, tmp = 1;
    ll tmp_n = n;                                                           //n的副本,因为下面是以n为运算的基础,一路除下去的,如果我们想要得到原始的n,就找不到了,这里给它照了一个像
    while (n) {                                                             //从后向前一位一位的讨论
        //代码中注释掉的两行,用于跟踪调试程序的分步运行情况
        //int t = res;
        if (n % 10 < x) res += n / 10 * tmp;                                //小于  n/10代表当前位数前面的数字值,比如785,tmp是指需要乘以10的几次方
        else if (n % 10 == x) res += n / 10 * tmp + (tmp_n % tmp + 1);      //等于  等于最麻烦,需要再次分情况讨论
        else res += (n / 10 + 1) * tmp;                                     //大于  这个加1,是因为是0~77,共78个
        n /= 10;                                                            //进入前一位
        tmp *= 10;                                                          //计算需要乘几个10的变量
        //cout << res - t << endl;
    }
    return res;
}

/**
对于0,稍作修改,
此时只需分成两类,因为不存在当前为小于0的情况,不过每次的最高为要排除全0的情况。
*/
ll count_0(ll n) {
    ll res = 0, tmp = 1;
    ll tmp_n = n;
    while (n) {
        //代码中注释掉的两行,用于跟踪调试程序的分步运行情况
        //int t = res;
        if (n % 10 == 0) res += (n / 10 - 1) * tmp + (tmp_n % tmp + 1);    //等于,剔除了大于0的情况
        else  res += (n / 10) * tmp;                                       //大于,剔除了大于0的情况

        n /= 10;                                                           //进入前一位
        tmp *= 10;                                                         //计算需要乘几个10的变量
        //cout << res - t << endl;
    }
    return res;
}


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

标签:tmp,10,338,int,res,ll,大于,计数问题,AcWing
来源: https://www.cnblogs.com/littlehb/p/15458237.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有