题目翻译:
给出一个数字n,求1~n的所有数字里面出现1的个数
题解思路:
如果采用暴力枚举绝对会T,所以就需要找规律,用数学的方法求解。直观地想法就是按位加和,比如说对于一个三位数,分别统计个位、十位、百位出现1的个数然后加和即可。
比如我们选取112这个三位数分析一下:
当我们的视角落到个位上时:(因为个位是0-9,所以我们只需要考虑个位前面的部分能让我们这个个位出现多少次1)
001
011
021
031
041
051
061
071
081
091
101
111
可见,一共出现了(左边部分+1)次,也就是(11+1)=12次1,因此个位为1的个数就是12。
当我们的视角落到十位上时:(因为十位是0-9,所以我们只需要考虑十位前面的部分以及十位后面的部分能让我们这个十位出现多少次1,注意现在前后两部分都对十位出现1的个数做出了限制)
为什么呢?我们可以写一下:
010
011
012
013
014
015
016
017
018
019
110
111
112
注意因为十位为1,并没有超过2,所以若百位为1的时候,十位为1的情况只有110-112,也就是3次,那如果这个数是122呢,是不是就可以从110-119了;因此,当十位为1的时候,十位出现1的次数=(左部)*10+(右部)+1 。当十位大于1的时候,不用考虑右部的约束,就相当于是(左部+1)*10.
当我们的视角落到百位上时:(因为百位是0-1,所以我们只需要考虑后边部分的限制,也就是百位为1时,后边部分+1)
100
101
102
103
104
105
106
107
108
109
110
111
112
此时百位1的左部是0,右部为0-12,依然满足上述公式(左部)*100+(右部)+1 .
总结一下:
我们将数字划分为左部left、现在关注的位置now(只有一位)、右部right。pos表示当前位置1会出现的次数,a表示now所代表的位数(比如个位数、百位数等)。则有如下公式:
pos = left * a + now * (right + 1) when now<=1
pos = (left + 1) * a when now>=2
也可以参考柳神的博客1049. Counting Ones (30)-PAT甲级真题(数学问题)
代码:
#include <iostream>
using namespace std;
int main() {
int n, left = 0, right = 0, a = 1, now = 0, ans = 0;
scanf("%d", &n);
while (n / a) {
left = n / (a * 10), now = n / a % 10, right = n % a;
if (now <= 1) ans += left * a + now * (right + 1);
else ans += (left + 1) * a;
a = a * 10;
}
printf("%d", ans);
return 0;
}
坑点:
无