这题就是数位dp的一道题目,我们首先把一个264范围内的数,也就是1018范围之内的数,给分解开来。
分解之后,我们深搜它,深搜函数的参数分别是,长度、是不是4、是不是上界(也就是是不是我们搜索的这个数是不是当前搜索位的最大值)。
对于深搜函数,我们的搜索边界就是,如果此时就剩下一位待搜索,也就是我们搜到了digit[0]的位置,我们就返回一个1值,此时条件即为len==0。
抑或我们搜索的过程中,这一位不是上界,而是小于上界的其他数,并且我们的dp数组更新过,里面不是零,我们就返回之前的值,也就是dp[len][is4]它的值。
它的值代表什么意思呢?它的意思就是说,我们之前搜索到len=2的时候,这一位不是4,然后我们对于它的个位搜索得到一个10,因为如果十位不是4的话,那这十个数里面肯定不会有49,所以dp[2][0]也就等于了10。
而当我们从其它的分支进来的时候,我们发下,此时len相同,并且这一位也不是4,我们不需要再进行搜索了,我们有之前存下的值。
这也就相当于是记忆化搜索了,相当于是一个非常优秀的剪枝。
对于相同的位数,如果是4,我们之前还没存下的话,我们就继续搜索,搜得不是上界之时的dp值,然后供其他的分支使用即可。
这是从深搜的角度解释记忆化搜索的,下面放一个视频解释,是另一个角度的,讲的还挺不错的。
https://www.bilibili.com/video/av27156563?from=search&seid=2518917738675420605
#include <iostream>
#include <cstring>
using namespace std;
int digit[20];
long long dp[20][2];
long long dfs(int len,bool is4,bool limit)
{
if (len==0)
return 1;
if (!limit&&dp[len][is4])
return dp[len][is4];
long long cnt = 0, up_bound = (limit ? digit[len] : 9);
for (int i = 0; i <= up_bound;i++) {
if (is4&&i==9)
continue;
cnt += dfs(len - 1, i == 4, limit && i == up_bound);
}
if (!limit)
dp[len][is4] = cnt;
return cnt;
}
long long solve(long long num)
{
int k = 0;
while (num) {
digit[++k] = num % 10;
num /= 10;
}
return dfs(k, false, true);
}
int main()
{
long long t, n;
cin >> t;
while (t--) {
cin >> n;
memset(dp, 0, sizeof(dp));
cout << n + 1 - solve(n) << endl;
}
return 0;
}