51Nod 1009 数字1的数量 数位规律

1. 题目描述

1.1. Limit

Time Limit: 1000 ms

Memory Limit: 131072 kB

1.2. Problem Description

给定一个十进制正整数 N N ,写下从1开始,到 N N 的所有正数,计算出其中出现所有1的个数。

例如: n = 12 n = 12 ,包含了5个1。1,10,12共包含3个1,11包含2个1,总共5个1。


1.3. Input

输入 N ( 1 N 1 0 9 ) N(1 \le N \le 10^9)


1.4. Output

输出包含1的个数


1.5. Sample Input

12

1.6. Sample Output

5

1.7. Source

51Nod 1009 数字1的数量


2. 解读

从低位到高位,依次考虑每一数位为1时,其他的数位能够取多少种情况。


定义以下符号:

pos: 数位
posNum: 数位上的数字
posScale: 当前数位代表的数量级, p o s S c a l e = 1 0 p o s posScale = 10^{pos}
left: 左边的数位
right: 右边的数位
current: 当前计数
ans: 累计计数


判断有多少种情况的方法如下:

  1. 若当前数位大于1,则将当前数位设置为1时
    • 左边的数位可以取 [ 0 , l e f t ] [0, left] ( l e f t + 1 ) (left + 1) 种情况
    • 右边的数位可以取 [ 0 , 1 0 p o s 1 ] [0, 10^{pos} - 1] p o s S c a l e posScale 种情况
    • 当前的情况计数为 ( l e f t + 1 ) × p o s S c a l e (left + 1) \times posScale
  2. 若当前数位等于0,则将当前数位设置为1时
    • 左边的数位可以取 [ 0 , l e f t ) [0, left) l e f t left 种情况
    • 右边的数位同上,共 p o s S c a l e posScale 种情况
    • 当前的情况计数为 l e f t × p o s S c a l e left \times posScale
  3. 若当前数位等于1,则将当前数位设置为1时
    • 左边的数位同上,共 l e f t left 种情况
    • 右边的数位同上,共 p o s S c a l e posScale 种情况
    • 还要加上左边数位取 l e f t left ,当前数位取1,右边的数位取 [ 0 , r i g h t ] [0, right] 的情况
    • 如计算123时,要考虑 [ 100 , 123 ] [100, 123] 这里的24种情况
    • 当前的情况计数为 l e f t × p o s S c a l e + r i g h t + 1 left \times posScale + right + 1

N = 123 N = 123 为例

pos posNum posScale left right current ans
0 3 1 12 0 13 13
1 2 10 1 3 20 33
2 1 100 0 23 24 57

得到的答案为57

3. 代码

#include <iostream>
using namespace std;
int Cal(int n)
{
    // 存储计数
    int ans = 0;
    // 取数位,从个位开始取到最高位
    int pos = 0;
    // 当前数位代表的数量级,posScale = 10^pos
    int posScale = 1;
    int posNum = 0, left = 0, right = 0;
    while (n / posScale) {
        // posNum,当前数位的数字
        posNum = (n / posScale) % 10;
        // 当前数位左边所有数位上的数字
        left = n / (posScale * 10);
        // 当前数位右边所有数位上的数字
        right = n % posScale;
        // 判断当前数位上的值
        if (posNum > 1) {
            // 若大于1,则将当前数位设置为1时
            // 左边的数位可以取 [0, left] 共 (left + 1) 种情况
            // 右边的数位可以取 [0, 10^pos - 1] 共 posScale 种情况
            ans += (left + 1) * posScale;
        } else if (posNum == 0) {
            // 若等于0,则将当前数位设置为1时
            // 左边的数位可以取 [0, left) 共 (left) 种情况
            // 右边的数位同上,posScale 种情况
            ans += left * posScale;
        } else if (posNum == 1) {
            // 若等于1,则将当前数位设置为1时
            // 左边的数位同上,left种情况
            // 右边的数位同上,posScale 种情况
            // 还要加上左边数位取left,当前数位取1,右边的数位取[0, right]的情况
            // 如计算123时,要考虑[100, 123]这里的24种情况
            ans += left * posScale + right + 1;
        }
        // 取下一个数位
        pos++;
        posScale *= 10;
    }
    return ans;
}
int main()
{
    int n;
    while (cin >> n) {
        cout << Cal(n) << endl;
    }
    return 0;
}

数位规律代码参考自一位博主的博客


还有一种动态规划方法,参考自另一位博主的博客

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long ll;
const int maxn = 20;
ll dp[maxn][maxn], a[maxn];

ll dfs(int pos, int num, int limit)
{
    if(pos == -1) return num;
    if(!limit && dp[pos][num] != -1) return dp[pos][num];
    int up = limit ? a[pos] : 9;
    ll tmp = 0;
    for(int i = 0; i <= up; i++)
        tmp += dfs(pos-1, num+(i==1), limit && i == a[pos]);
    if(!limit) dp[pos][num] = tmp;
    return tmp;
}

ll solve(ll x)
{
    int pos = 0;
    while(x)
    {
        a[pos++] = x%10;
        x /= 10;
    }
    return dfs(pos-1, 0,  1);
}

int main(void)
{
    ll n;
    memset(dp, -1, sizeof(dp));
    while(cin >> n)
        printf("%lld\n", solve(n));
    return 0;
}

联系邮箱:[email protected]

Github:https://github.com/CurrenWong

欢迎转载/Star/Fork,有问题欢迎通过邮箱交流。

猜你喜欢

转载自blog.csdn.net/qq_41729780/article/details/105826663