数学专题
作者:互联网
Counting Ones (30)
Link
题意:给定一个数n,求出1~n这n个十进制数中1出现的次数。例如11中1出现了2次,10中出现了1次。
思路:对于n,假设它写成十进制有m位,表示为:
\(a_1 a_2 a_3 \dots a_m\)
其中 \(n=a_1*10^{m-1}+a_2*10^{m-2}+\dots +a_{m-1}*10+a_m\).
那么就对这m位从低到高遍历一遍,每次将这m个数按照当前位划分为三组:left, now, right
第1次:
\(a_ma_{m-1}\dots a_{2}\), \(a_{1}\), \(0\)
第2次:
\(a_ma_{m-1}\dots a_{3}\), \(a_{2}\), \(a_{1}\)
第3次:
\(a_ma_{m-1}\dots a_{4}\), \(a_{3}\), \(a_{2}a_{1}\)
......
第m次:
\(a_m\), \(a_{m-1}\), \(a_{m-2}\dots a_{2}a_{1}\)
这样划分的作用很明显。因为当前位now的值很重要:
例如21031这个数,当前位是0,那么left=21, now=0, right=31。
此时,我们想要找出不超过n的数中now位(即从低到高的第3位,百位)为1的数的个数。
但是现在now位为0,相当于是要将now位增大,那么只好借位了——那就是让left变小。left本来是21,那么我们就取left在[0,20]这个闭区间内的值,这样now位就可以取[0,9]中的任意值了!但是我们只需要1,这样我们就可以计算出now位为1的数的个数。
left确定了,那么right该怎么取值呢?
如果left<21,那么right可以取[0,99]中的任意数;如果left==21
,那么right只能取[0,31]中的数,而且此时now必须为0。但是现在我们考虑now==1
的情况,因此right可以取[0,99]中的任意数。综上,now位为1的不大于n的数为(20-0+1)*(99-0+1)=2100
。
在遍历每一位的过程中,我们设置了a来表示当前位处于整个数的什么位置——如果当前位是个位,a就是1;如果当前位是十位,a就是10…以此类推。因此,right部分的取值范围是[0,a-1]这个闭区间,总个数是 a-1-0+1=a
。
刚才是 now==0
的情况。如果 now==1
,那么当left取[0,left-1]时,right可以取[0,a-1];当left取值就是left时,right只能取[0,right],即right+1个数。
如果 now>2
,那么left取[0,left],right取[0,a-1]。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <string>
#include <string.h>
#include <vector>
using namespace std;
int n,ans;
int main() {
scanf("%d",&n);
int a=1,left,now,right;
while(n/a){
left=n/(a*10),now=(n/a)%10,right=n%a;
if(now==0) ans+=left*a;
else if(now==1) ans+=left*a+right+1;
else ans+=(left+1)*a;
a*=10;
}
printf("%d\n",ans);
return 0;
}
标签:dots,10,专题,include,right,数学,now,left 来源: https://www.cnblogs.com/preccrep/p/16412997.html