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;
}
如果有不对的地方还望各位大佬耐心指点,谢谢~