hdu3555 Bomb (秒懂的数位dp)

来源:互联网 发布:福州天趣网络 编辑:程序博客网 时间:2024/06/07 13:39

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 longusing 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;}
贴了代码:才发现没说记忆化的问题。算啦,有时间再补,当然,如果有人需要,请留言。我看到后,马上补齐。敲了这么多,如果有帮助留言顶一下,点个赞。如果有叙述错误,也请指出,共同进步。