The counter-terrorists found a time bomb in the dust. But this time the terrorists improve on the time bomb. The number sequence of the time bomb counts from 1 to N. If the current number sequence includes the sub-sequence “49”, the power of the blast would add one point.
Now the counter-terrorist knows the number N. They want to know the final points of the power. Can you help them?
Input
The first line of input consists of an integer T (1 <= T <= 10000), indicating the number of test cases. For each test case, there will be an integer N (1 <= N <= 2^63-1) as the description.
The input terminates by end of file marker.
Output
For each test case, output an integer indicating the final points of the power.
Sample Input
3
1
50
500
Sample Output
0
1
15
Hint
From 1 to 500, the numbers that include the sub-sequence “49” are “49”,”149”,”249”,”349”,”449”,”490”,”491”,”492”,”493”,”494”,”495”,”496”,”497”,”498”,”499”,
so the answer is 15.
题意:
就是找从0到n的数中含有49的数字个数,4和9必须相邻。
解题思路:
1.先说一下数位dp,数位,就是数字的个,十,百,千位,etc。在数位上dp,在很多人看完数位dp后会说这不是个记忆化搜索吗?其实dp就是由记忆化变形过来的,俩者的复杂度几近相同。这个观点可以看“挑战程序设计”这本书验证。
2.进入正题。
首先在数位上dp,我们就需要把这个数的每一位分解出来。具体看下面代码块。
int len = 0; //x为要分解的数,分解出来储存在a数组中。a是全局数组,大小定位你要分解的最大数的位数
while(x)
{
a[len++] = x % 10;
x /= 10;
}
分解出来,干什么呢?下面就要先看题目要求,拿本题来说,要找含有49的数字个数。但是直接找含有49的数字较为麻烦,那我
们可以找到不含49的数字个数,然后拿总数相减就可以得到答案。
3.那现在任务变成求,不含49的数字个数。
先上个最简单的数位dp板子,帮助理解。
ll dfs(int pos,int pre,bool limit)//pos表示现在是数的第几位,pre是前一位数位上的数,limit是核心,一会儿细讲
{
if(pos == -1) //应为数位数组a是从0开始存储,所以pos变为-1表示这个数每一位都遍历完了,就返回 1
return 1; //1表示搜索结束,找到一个合法数字
int up = limit?a[pos]:9; //核心:细讲
ll ans = 0; //统计合乎要求的数
for(int i = 0;i <= up;i++) //遍历当前数位上可能取的每一个数
{
if(pre == 4 && i == 9) //当前一位是4,且当前位是9的我们就不往下遍历,最后含有49的自然就没计数
{
continue;
}else{
ans += dfs(pos-1,i,limit && i == a[pos]); //不含49,就往下遍历pos是从最大数位开始
}
}
return ans;
}
4.limit这是个好东西,能想出来的人,我现在先Orz(膜拜)一下。
5752,比如给这个数,我们从最高位去遍历,那么最高位是千位,可以取的数字是0,1,2,3,4,5.
至于这里的前导零先不管。我们看一下取0~4和取5,有什么分别? 先闭上眼睛想想。
。
。
。
取0~4,,,那么百位,可以取哪几个数? 答案:0--9
.
取5呢,,,那么百位就只能取0--7.。。这里能想通吧。因为一个数是从0一直增大,但是不能超过所给的数字吧
那么来看代码。
int up = limit?a[pos]:9;
如果有限制,limit为真,那么我们遍历这个数位的上限up就是a[pos],a[pos]就是存储的当前最大位。就是千位的7.
如果没有限制,limit为假,那么up就是9.
。
那么我们关心的问题就是什么时候limit为真。对应于刚刚举得例子就是
千位取5,遍历百位的时候0--7.
百位取7,遍历十位的时候0--5.
也就是说遍历当前位的时候,如果前一位是最大的那个,那么limit为真。
。。
ans += dfs(pos-1,i,limit && i == a[pos]);
。
当往下遍历时,为什么限制limit的值是“limit && i == a[pos]”这个东西。
。
我们想i是从0遍历到上限。如果i==a[pos],那么就是说i==当前位最大的那个数。那么下一位是不是受限制了?
所以当相等且limit为真,下一位受限制。limit就为真。为什么要“limit && i == a[pos]”这个式子
要且上一个limit,limit表示当前位受前一位限制,
。。
我举个例子还是5752
.。
如果前位取4.limit为假,那么百位取到a[pos]也就是7的时候受限制吗???写几个数就懂了。
5.下面说循环遍历的注意点。hdu有个不要62的题。
。
对于每一个题,我们根据要求改变条件,就可以得到不同的题目要求的答案。例如本题,我们要找不含49,那么
根据pre和当前位判断,如果含49,就跳过。如果是不要62,那么也是一样。自己思考。
AC代码:
# include <cstdio>
# include <cstring>
# define ll long long
using namespace std;
ll dp[50][10];
int a[50];
ll dfs(int pos,int pre,bool limit)
{
if(pos == -1)
return 1;
if(!limit && dp[pos][pre] != -1)
{
return dp[pos][pre];
}
int up = limit?a[pos]:9;
ll ans = 0;
for(int i = 0;i <= up;i++)
{
if(pre == 4 && i == 9)
{
continue;
}else{
ans += dfs(pos-1,i,limit && i == a[pos]);
}
}
if(!limit)
{
dp[pos][pre] = ans;
}
return ans;
}
ll solve(ll x)
{
int len = 0;
while(x)
{
a[len++] = x % 10;
x /= 10;
}
return dfs(len-1,0,1);
}
int main()
{
int T;
ll ri;
scanf("%d",&T);
memset(dp,-1,sizeof(dp));
while(T--)
{
scanf("%lld",&ri);
printf("%lld\n",ri-solve(ri)+1);
}
return 0;
}
贴了代码:才发现没说记忆化的问题。算啦,有时间再补,当然,如果有人需要,请留言。我看到后,马上补齐。
敲了这么多,如果有帮助留言顶一下,点个赞。如果有叙述错误,也请指出,共同进步。