PTA---Count One

PTA---Counting Ones(数位DP法)

题目描述这里就不贴了(浪费空间QAQ,这里给个传送门)

看见很多题解都是用数论方法做的,对于我一个数学菜鸡也只能表面上让自己懂了QvQ...

So,这篇博客将会告诉你什么是数位DP,以及这个题目怎么用数位DP来做

数位DP简介

《很简单》就是对于数字的位数进行动态规划的一种方法

适用于当数字特别大的时候的一种小技巧,比如我们要算1-10^128之中连续出现49的个数,如果我们直接暴力枚举每个数字,毫无疑问的超时

而数位DP应运而生

而对于动态规划,我们都会列出一个状态方程,对于数位DP,它的状态方程一般是固定的,DP[n][10],

例如DP[i][3](假设问题为连续出现49的个数)

代表数字为i位,开头为3的这些数字中连续出现49的个数

而我们知道第i个位置会进一步影响到第i-1个位置的个数

嗯...所以就有DP[i-1]和DP[i]的小联系咯

言归正传

唠了一大堆,我知道还是很难懂的...

最好是从一道题中进行分析,这样才能加深理解...

接下来正片开始

题目大致意思

读入一个数X,求1-X范围内所有数中1出现的个数

思路

我们由一个小小的例子来引出所有的解:

就拿样例12来看吧

首先我们会把这个数字所有的位数取出,也就是a[2] = {2,1}

对于DP[2][1]代表的数字范围是10~19,DP[2][0]代表00~09

然后我们先看高位,当高位是0的时候

这时代表的数字范围是0~9,只有1符合条件,返回1

高位是1时,这时候低位可以是0,1,2(当然这个时候我们得特判才能得到这个结果),

由于1是我们想要看到的,然后我们特判这个1是不是所属数字最大的,如果是,那么它的取值就有范围,这里是0~2

如果不是那么我们就能取10~19的数字了,这样我们就需要加上pow(10,len),len为当前位数

最后,处理完这一位的时候,我们递归地去处理len-1位的数字

由上面分析可知,我们需要保存前一位是不是已经最大了,如果是最大了我们这一位的最大值就受限制(不能是9了,而是我们输入的某一个)

QAQ,还是好复杂,我们照着代码来一遍吧

int cal()
{
    int k = x;
    int len = 0;
    while(k)
    {
        num[len++] = k%10;
        k/=10;
    }
    return dfs(len-1,1,0);
}

cal函数就是返回解的,这里我们先对输入的数字x进行处理,将每一位处理放到num中

int dfs(int len,int fp,int ws)
{
    if(len == -1)
    {
        return 0;
    }
    if(!fp && dp[len][ws])  return dp[len][ws];
    
    int Max = fp ? num[len]:9;
    int ans = 0;
    for(int i=0;i<=Max;i++)
    {
        if(i == 1)
        {
            if(i == Max)
            ans += x%ksm(10,len) + 1;
            else
            ans += ksm(10,len);
        }
        ans += dfs(len-1,fp && i == Max,i);
    }
    return fp ? ans : dp[len][ws] = ans;
}

然后就是这个核心递归了

len是当前位数,fp是判定前一位是不是最大值,ws是当前求的那个最高位的数值,对于DP[len][ws]

1.如果len == -1这时候肯定是不存在1的,返回0

2.如果当前位数是1,我们看是不是最高位,是的话我们当前位数的解只和1xxxxx,中xxxxx有关,否则例如对于2xxxxxx这种遍历到1时,应该是加上100000

3.然后对于ixxxxxxx我们再去找i后边所有数的中1的个数

4.对于记忆化,我们在最后的Return和开头第二个IF中体现,这样就可以把我们之前已经求出来的结果直接拿出来,省下重复计算的时间

5.QAQ就到这啦

完整代码

#include <iostream>
using namespace std;
int dp[100][10];
int num[100];
int x;
int ksm(int a,int b)
{
    int t = 1;
    while(b)
    {
        if(b&1) t = t*a;
        a = a*a,b>>=1;
    }
    return t;
}
int dfs(int len,int fp,int ws)
{
    if(len == -1)
    {
        return 0;
    }
    if(!fp && dp[len][ws])  return dp[len][ws];
    
    int Max = fp ? num[len]:9;
    int ans = 0;
    for(int i=0;i<=Max;i++)
    {
        if(i == 1)
        {
            if(i == Max)
            ans += x%ksm(10,len) + 1;
            else
            ans += ksm(10,len);
        }
        ans += dfs(len-1,fp && i == Max,i);
    }
    return fp ? ans : dp[len][ws] = ans;
}
int cal()
{
    int k = x;
    int len = 0;
    while(k)
    {
        num[len++] = k%10;
        k/=10;
    }
    return dfs(len-1,1,0);
}
int main()
{
    cin>>x;
    cout<<cal();
    return 0;
}

如果有不对的地方还望各位大佬耐心指点,谢谢~

posted @ 2021-03-03 17:54  K0njac  阅读(58)  评论(0编辑  收藏  举报