题目链接地址:
九度OJ-题目1373:整数中1出现的次数(从1到n整数中1出现的次数)
题目描述:
亲们!!我们的外国友人YZ这几天总是睡不好,初中奥数里有一个题目一直困扰着他,特此他向JOBDU发来求助信,希望亲们能帮帮他。问题是:求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数。
输入:
输入有多组数据,每组测试数据为一行。
每一行有两个整数a,b(0<=a,b<=1,000,000,000)。
输出:
对应每个测试案例,输出a和b之间1出现的次数。
样例输入:
0 5
1 13
21 55
31 99
样例输出:
1
6
4
7
解题思路:
这道题最直观的想法应该是遍历a和b之间的所有整数,然后依次计算出每个数字中1出现的次数,再累加起来就能得出a和b之间1出现的次数了。但是这种算法肯定会超时。之前在《编程之美》上见过这道题,书上的解法是先分别统计整数n中的每个十进制位中1出现的次数,再累加起来就能得出区间[1,n]中1出现的次数。因此我的想法是:先统计出区间[1,a-1]中1出现的次数countA,然后统计出区间[1,b]中1出现的次数countB,那么countB – countA就是区间[a,b]中1出现的次数了。接下来问题就转换成求区间[1,n]中1出现的次数了。
求区间[1,n]中1出现的次数就是对数字n中的每个进制位数字与1进行比较:
1)如果进制位数字大于1,则当前进制位1出现的次数仅由高进制位数字确定;
2)如果进制位数字等于1,则当前进制位1出现的次数由高进制位数字和低进制位数字共同确定;
3)如果进制位数字小于1,则当前进制位1出现的次数仅由高进制位数字确定。
举个栗子,下面是求从1到1012中1出现的次数的步骤:
(1)先判断1012的个位数2,因为2 > 1,所以区间[1, 1012]中个位数出现1的次数仅由1012的高3位数(000 ~ 101)确定,
个位数1出现的次数是(101 + 1) * 1=102次,分别是:(000 ~ 101)1;
(2)再判断1012的十位数1,因为1 = 1,所以区间[1, 1012]中个位数出现1的次数由1012的高2位数(00~09)和最低位数(0~2)
共同确定。先算出区间[1, 999]中十位数出现1的次数是10 * 10 = 100次,分别是:(00 ~ 09)1(0 ~ 9),再算出
区间[1000,1012]中十位数出现1的次数有3次,分别是101(0 ~ 2),因此区间[1, 1012]中十位数出现1的次数为100 + 3 = 103;
(3)再判断1012的百位数0,因为0 < 1,所以区间[1, 1012]中百位数出现1的次数仅由1012的最高位数1确定,
区间[1,1012]中百位数出现1的次数是1 * 100=100次,分别是01(00 ~ 99);
(4)最后判断1012的千位数1,因为1 = 1,所以区间[1, 1012]中千位数出现1的次数由1012的高位数字0和低3位数字(000~012)
共同确定,因此区间[1, 1012]中千位数出现1的次数是0 * 1000 + 13 = 13次,分别是1(000 ~ 012);
综上可知,区间[1,1012]中1出现的次数是102 + 103 + 100 + 13 = 318次。
此外这道题还有个坑爹的地方需要注意:请看题目的描述“对应每个测试案例,输出a和b之间1出现的次数”,而不是“输出区间[a,b]中1出现的次数”,所以输入的第一个整数a可能大于第2个整数b ,如果输入的a > b,必须要先交换a与b。
AC代码如下:
#include<stdio.h> /** * 统计区间[1,n]中1出现的次数 * @param n 输入的整数n * @return count 返回区间[1,n]中1出现的次数 */ long long countOneFromOneToN(int n) { long long count = 0; // 统计1 ~ n中1出现的次数 if(n < 1) return count; else { int tempN = n; // 用于保存处理过程中的n int lowBits = 0; // 整数n中比当前进制位更低的低位部分 int highBits = n; // 整数n中比当前进制位更高的高位部分 int currentDecimalBit = 0; // 整数n当前被处理数位所对应的数字,最先处理个位上的数字 int currentDecimalWeight = 1; // 整数n当前正在处理的进制位,最先处理个位 while(tempN > 0) { highBits = n / (10 * currentDecimalWeight); // 求高位部分 if(currentDecimalWeight > 1) { lowBits = n % currentDecimalWeight; // 求低位部分 } currentDecimalBit = tempN % 10; // 求正在被处理的当前位 // 将当前位与1进行比较,然后分类讨论统计1出现的次数 if(currentDecimalBit > 1) { count += (highBits + 1) * currentDecimalWeight; } else if(currentDecimalBit == 1) { count += highBits * currentDecimalWeight + lowBits + 1; } else { count += highBits * currentDecimalWeight; } tempN = tempN / 10; currentDecimalWeight = 10 * currentDecimalWeight; // 跳到n的下一个高位,例如从个位跳到十位 } return count; } } /** * 交换两个整数 * @param a 整数a的地址 * @param b 整数b的地址 * @return void */ void swap(int * a,int * b) { int temp; temp = *a; *a = *b; *b = temp; } int main() { long long result; // [a,b]区间中1出现的次数 long long CountA; // [1,a-1]区间中1出现的次数 long long CountB; // [1,b]区间中1出现的次数 int a,b; while(EOF != scanf("%d%d",&a,&b)) { if(a > b) // 如果输入的a比b大,则需要交换两个数 { swap(&a,&b); } CountA = countOneFromOneToN(a - 1); // 统计[1,a-1]区间中1出现的次数 CountB = countOneFromOneToN(b); // 统计[1,b]区间中1出现的次数 result = CountB - CountA; printf("%lld\n",result); } return 0; } /************************************************************** Problem: 1373 User: blueshell Language: C Result: Accepted Time:0 ms Memory:912 kb ****************************************************************/