zoukankan      html  css  js  c++  java
  • 数位dp入门 HDU 2089 HDU 3555

      最基本的一类数位dp题,题目大意一般是在a~b的范围,满足某些要求的数字有多少个,而这些要求一般都是要包含或者不包含某些数字,或者一些带着数字性质的要求,一般来说暴力是可以解决这一类问题,可是当范围非常大时,暴力明显会超时,这时便是需要把它转化为一类dp问题,这就是数位dp。像一个数1234567890,直接把它暴力的话,那它就是一个1e9级别的数,可是把它一个数位一个数位的话,那它就是1位是0,2为是9,一直到10位是1,一共才10位的长度,明显就比暴力明朗多了。

      由于我也是刚接触不久,还没有什么自己深刻感悟,日后再做总结,现在先具体问题具体分析,来两道入门的数位dp题进行进行理解

    不要62和4HDU - 2089 

      题目大意就是,一个数字它如果含有62或者4它就是不吉利数字,而你要求的就是【n,m】范围内的不含有不吉利数字的个数。

      这题的范围只有1e6,所以暴力还是行得通的,不过我们在学数位dp,就用数位dp来解决。那我们想一下,每一个数个位,十位,百位。。。的范围都是从0~9,那么我们把一个数拆成一位位,例如,一个万位的数就可以拆成

    万:0 1 2 3 4 5 6 7 8 9

    千:0 1 2 3 4 5 6 7 8 9

    百:0 1 2 3 4 5 6 7 8 9

    十:0 1 2 3 4 5 6 7 8 9

    个:0 1 2 3 4 5 6 7 8 9

      现在假如一个数的10086,那它的范围内有多少个不含不吉利吉利的数呢,首先我们得把10086分解,得出一个上界,类似这个样子

    万:0 1 2 3 4 5 6 7 8 9 上界1

    千:0 1 2 3 4 5 6 7 8 9 上界0

    百:0 1 2 3 4 5 6 7 8 9 上界0

    十:0 1 2 3 4 5 6 7 8 9 上界8

    个:0 1 2 3 4 5 6 7 8 9 上界6

      这个上界就是在有限制的情况下,当前数位的数字选择只能从[0,上界]中选择,那么这个限制又是什么吗,其实就是范围限制,就像现在我们要求的是10086范围内的,那么如果万位取0,对千位肯定就没有限制可以取[0,9]中任意的数字,但当万位取1,千位就有限制了只能取到0,要是取到1那么就不是求10086范围内的了,而是求11xxx范围内的了,那这个限制怎么传递呢,首先当前数位如果没有限制的话,那么对下面的数位也没有限制,像万位取0,下面的数位都没有限制,而当前数位如果有限制的话,并且取的是上界的话,对下面的数位就有限制,像万位取1,千位有限制,然后千位取0是上界又对下面的又有限制,而像万位取1,千位取0,百位取0,对十位有限制,但十位取小于8的数对个位就没有限制,所以上界的限制传递的条件就是,当前数位有限制并且取的是上界,就会对下面的数位有限制

      那现在回到问题,怎么确定不是不吉利的数有几个呢,首先每一位肯定是不能含有4这个数字的,然后就是上一数位是6,当前数位就不能是2,详情见代码

     1 #include<cstdio>
     2 #include<cstring>
     3 int n,m,up[18],dp[65][2];//dp[i][j]数位i的上一数字是不是6时不含不吉利的数字有多少个 
     4 int dfs(int p,bool is6,bool isu)//p当前数位,is6上一个数位是不是6,isu当前数位有没有限制 
     5 {
     6     if(p==0)//边缘值,p为0是个空集,肯定没有62或4,返回1 
     7         return 1;
     8     if(!isu&&dp[p][is6]!=-1)
     9         return dp[p][is6];
    10     int ans=0;
    11     for(int i=0;i<=(isu ? up[p] : 9);i++)
    12     {
    13         if((is6&&i==2)||i==4)//当前数位不能取4,以及如果上一数位取6,当前数位不能取9 
    14             continue;
    15         ans+=dfs(p-1,i==6,isu&&i==up[p]);//继续往下判断下一数位 
    16     }
    17     if(!isu)
    18         dp[p][is6]=ans;
    19     return ans;
    20 }
    21 int solve(int x)
    22 {
    23     int num=0;
    24     while(x)
    25     {
    26         up[++num]=x%10;
    27         x/=10;
    28     }
    29     return dfs(num,0,1);
    30 }
    31 int main()
    32 {
    33     memset(dp,-1,sizeof(dp));
    34     while(scanf("%d%d",&n,&m)&&(n||m))
    35         printf("%d
    ",solve(m)-solve(n-1));
    36     return 0;
    37 }
    666

      关于记忆化,当前数位没有限制时,那么它取任何数相对应的结果是通用的,记忆化搜索就达到了省时的效果

    要49HDU - 3555 

      相比上一题不要62和4,这题就是给出一个n,要求[1,n]范围内含49的数字有多少个,不过我们可以反过来求出不含49的,然后减去即可

    #include<cstdio>
    #include<cstring>
    #define ll long long
    ll n,dp[65][2];
    int t,up[18];
    ll dfs(int p,bool is4,bool isu)
    {
        if(p==0)
            return 1;
        if(!isu&&dp[p][is4]!=-1)
            return dp[p][is4];
        ll ans=0;
        for(int i=0;i<=(isu ? up[p] : 9);i++)
        {
            if(is4&&i==9)
                continue;
            ans+=dfs(p-1,i==4,isu&&i==up[p]);
        }
        if(!isu)
            dp[p][is4]=ans;
        return ans;
    }
    ll solve(ll x)
    {
        int num=0;
        while(x)
        {
            up[++num]=x%10;
            x/=10;
        }
        return dfs(num,0,1);
    }
    int main()
    {
        scanf("%d",&t);
        memset(dp,-1,sizeof(dp));
        while(t--)
        {
            scanf("%lld",&n);
            printf("%lld
    ",n+1-solve(n));
        }
        return 0;
    }
    999

      对于答案的+1,因为我们的数位dp求的是[0,n]范围内的不含49的数字的个数,而题目要求的是[1,n]范围内含49的数字的个数,直接用n减去的话,0肯定不含49,会多减去一个0,所以要+1

    当然也有直接就是求[1,n]范围内含49的数字个数的,详情见代码

     1 #include<cstdio>
     2 #include<cstring>
     3 #define ll long long
     4 ll n,dp[65][2],cf[18]={1};//cf保存10的次方 
     5 int t,up[18];
     6 ll dfs(int p,bool is4,bool isu)
     7 {
     8     if(p==0)//空集不含49,返回0 
     9         return 0;
    10     if(!isu&&dp[p][is4]!=-1)
    11         return dp[p][is4];
    12     ll ans=0;
    13     for(int i=0;i<=(isu ? up[p] : 9);i++)
    14     {
    15         if(is4&&i==9)//上一数位的4,当前位是9,可以计算答案了 
    16             ans+=isu ? n%cf[p-1]+1 : cf[p-1];//有上界的限制,比如n是49870,
    17         //当前位是第4位取9,那么49000~49870都是可以的,也就是n%1000+1
    18         //反之没有限制的话,比如n是50000,当第5位是4,当前位是第4位取9
    19         //那么49000~49999都是可以的,也就是1000 
    20         else
    21             ans+=dfs(p-1,i==4,isu&&i==up[p]);
    22     }
    23     if(!isu)
    24         dp[p][is4]=ans;
    25     return ans;
    26 }
    27 ll solve(ll x)
    28 {
    29     int num=0;
    30     while(x)
    31     {
    32         up[++num]=x%10;
    33         x/=10;
    34     }
    35     return dfs(num,0,1);
    36 }
    37 int main()
    38 {
    39     for(ll i=1;i<=18;i++)
    40         cf[i]=cf[i-1]*10;
    41     scanf("%d",&t);
    42     memset(dp,-1,sizeof(dp));
    43     while(t--)
    44     {
    45         scanf("%lld",&n);
    46         printf("%lld
    ",solve(n));
    47     }
    48     return 0;
    49 }
    999
  • 相关阅读:
    kafka 配置属性
    mybatis 启动流程源码分析(二)之 Configuration-Properties解析
    mybatis 配置文件
    mybatis 启动流程源码分析(一)
    mybatis configuration
    使用函数式编程替换if-else
    mybatis 基本使用
    第十二周学习笔记
    T-SQL的进阶:超越基本级别3:构建相关子查询——701小组
    第十周学习笔记
  • 原文地址:https://www.cnblogs.com/LMCC1108/p/10457202.html
Copyright © 2011-2022 走看看