题目链接:51nod1009


基准时间限制:1 秒 空间限制:131072 KB 分值: 5  难度:1级算法题
 收藏
 关注
给定一个十进制正整数N,写下从1开始,到N的所有正数,计算出其中出现所有1的个数。
例如:n = 12,包含了5个1。1,10,12共包含3个1,11包含2个1,总共5个1。
Input
输入N(1 <= N <= 10^9)
Output
输出包含1的个数
Input示例
12
Output示例
5

题目意思:给你一个数n求1-n中所有1的个数,,具体见题目描述


 题目思路: (第一次写数位dp,,之前看过别人怎么写,这次自己总算是写出来了,,,好菜啊) dp [i] [j] 表示从1到以j开头的 i 位数的1的个数 例如 dp [2][2] 就表示 1 - 29的1的个数  dp [5][2] 就表示1-29999 的1的个数 ,,,那么不难推出转移方程为 dp [i] [j] = dp [i] [j-1] + dp [i-1] [9] ,,,当j=1 时 dp [i] [j] 要加上 10^(i-1) 

 还有就是 dp [i]  [0]  = dp [i-1][9]+1; 这个方程可以自己去模拟模拟,还是不难得出的,,,最后求得时候就是按位来求,,把n拆分成 1位的数 + 2位的数+ ....+n的位数的数

然后就是把没位的答案加起来,,,例如  1234  拆分成 1000  + 200 + 30 +4  所以就等于dp [1][3] +dp [2] [2] +(dp [4] [0] +n-1000) (这里是开头为1的话就要加上后面位数的数)


AC代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

int dp[10][10];
void init()
{
    memset(dp,0,sizeof(dp));
    int num=1;
    for(int i=1; i<=9; i++)
    {
        dp[i][0]=dp[i-1][9]+1; //处理以0开头
        for(int j=1; j<=9; j++)
        {
            dp[i][j]=dp[i][j-1]+dp[i-1][9]; //转移方程
            if(j==1)
            {
                dp[i][j]+=num-1; //以1开头的数
            }
        }
        num*=10;
    }
}
int main()
{
    init();
    int n;cin>>n;
    int num=0; //记录位数
    int m=n;
    while(m)   //统计位数
    {
        num++;
        m/=10;
    }
    m=1;
    int ans=0;
    for(int i=1; i<num; i++)
        m*=10;
    while(num)
    {
        int x=n/m;
        if(x)
        {
            ans+=dp[num][x-1]; //每个位数的答案相加
            if(x==1)
            {
                ans+=(n-x*m); //以1开头的情况
            }
        }
        num--;
        n-=(m*x); //得到下一位数
        m/=10;
    }
    cout<<ans<<endl;
    return 0;
}