1到n中1的出现次数
题目描述
给定一个整数n,返回从1到n的数字中1出现的个数。
例如:
n = 5 , 1 ∼ n n=5, 1 \sim n n=5,1∼n为1, 2, 3, 4, 5。1,2,3,4,5。那么1出现了1次,所以返回1。
n = 11 , 1 ∼ n n=11, 1 \sim n n=11,1∼n为1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11。1,2,3,4,5,6,7,8,9,10,11。那么1出现次数为1(出现1次),10(出现1次),11(有两个1,所以出现了2次),所以返回4。
输入描述:
输入一个整数N。
输出描述:
输出一个整数表示答案
示例1
输入
5
输出
1
示例2
输入
11
输出
4
示例3
输入
2345
输出
1775
备注:
1 ⩽ N ⩽ 1 0 13 1 \leqslant N \leqslant 10^{13} 1⩽N⩽1013
解法一(计数问题):
由低位到高位考虑每一位上出现 1 的次数。假设数字为 12x45 ,百位为 x ,分情况讨论 x:
- 若 x==0 ,那么百位为 1 的数字有:[00~11] 1 [00~99],共有 12 * 100 = 1200种;
- 若 x==1,那么百位为 1 的数字有:[00~11] 1 [00~99] + 121[00~45],共有 12*100 + 46 = 1246种;
- 若 x>1,那么百位为 1 的数字有:[00~12] 1 [00~99],共有 13 * 100 = 1300种
于是,我们可以把一个数字拆分成三部分:left cur right,根据 cur 的情况选择上述讨论计算即可,假设 cur 为第 i 位(从0开始):
- 若 cur==0 , s u m + = l e f t ∗ 1 0 i sum += left * 10^i sum+=left∗10i
- 若 cur==1, s u m + = l e f t ∗ 1 0 i + r i g h t + 1 sum += left * 10^i + right + 1 sum+=left∗10i+right+1
- 若cur > 1, s u m + = ( l e f t + 1 ) ∗ 1 0 i sum += (left + 1) * 10^i sum+=(left+1)∗10i
解法一代码:
#include <cstdio>
using namespace std;
typedef long LL;
int main(void) {
LL n;
scanf("%ld", &n);
LL high = n / 10, cur = n % 10, low = 0;
LL digit = 1;
LL ret = 0;
while ( high || cur ) {
ret += high * digit;
if ( cur == 1 ) ret += low + 1;
else if ( cur > 1 ) ret += digit;
low += cur * digit;
cur = high % 10;
high /= 10;
digit *= 10;
}
return 0 * printf("%ld\n", ret);
}
解法二(数位DP):
参考 数位DP
解法二代码:
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 14;
long f[N][N];
int nums[N];
long n;
long dfs( int pos, long cnt, bool limit ) {
if ( pos == -1 ) return cnt;
if ( !limit && ~f[pos][cnt] ) return f[pos][cnt];
int up = limit ? nums[pos] : 9;
long ans = 0;
for ( int i = 0; i <= up; ++i )
ans += dfs( pos - 1, cnt + (i == 1), limit && (i == up) );
if ( !limit ) f[pos][cnt] = ans;
return ans;
}
void solve() {
memset( f, -1, sizeof f );
int p = 0;
while ( n ) {
nums[p++] = n % 10;
n /= 10;
}
printf("%ld\n", dfs( p - 1, 0, true ));
}
int main(void) {
scanf("%ld", &n);
solve();
return 0;
}