zoukankan      html  css  js  c++  java
  • HDU2089-不要62

    不要62

    Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
    Total Submission(s): 38536    Accepted Submission(s): 13987


    Problem Description
    杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer)。
    杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。
    不吉利的数字为所有含有4或62的号码。例如:
    62315 73418 88914
    都属于不吉利号码。但是,61152虽然含有6和2,但不是62连号,所以不属于不吉利数字之列。
    你的任务是,对于每次给出的一个牌照区间号,推断出交管局今次又要实际上给多少辆新的士车上牌照了。
     

    Input
    输入的都是整数对n、m(0<n≤m<1000000),如果遇到都是0的整数对,则输入结束。
     

    Output
    对于每个整数对,输出一个不含有不吉利数字的统计个数,该数值占一行位置。
     

    Sample Input
    1 100 0 0
     

    Sample Output
    80
     


    思路:这是一道数位DP的入门题目。先附上ppt的下载链接,讲得很不错的。链接:点击打开链接

    首先是初始化dp数组。dp数组保存的就是以  j  为开头的  i  位数字符合要求的数字有多少个。

    void init()
    {
        memset(dp,0,sizeof(dp));
        dp[0][0]=1;//看题目要求,一般状态下以0为开头的0位数状态为1
        for(int i=1;i<=7;i++)//一共最大是7位数
        {
            for(int j=0;j<=9;j++)//从右往左,先模拟第 i 位的取值
            {
                for(int k=0;k<=9;k++)//再摸拟第  i-1 位的取值
                {
                    if(j!=4&&!(j==6&&k==2))//判断是否符合条件描述
                    {
                        dp[i][j]+=dp[i-1][k];//如果满足条件相当于第 i 位(取值为j)共 i 位数的状态上加上第 i-1 位(取值为k)共 i-1 位的状态
                    }
                }
            }
        }
    }

    然后计算出来digit数组的值,把数倒序保存在数组中

    int cntlen(int n)
    {
        int len=0;
        while(n)
        {
            len++;
            n/=10;
        }
        return len;
    }
    void cal(int n, int len)
    {
        memset(digit,0,sizeof(digit));
        for(int i=1;i<=len;i++)
        {
            digit[i]=n%10;
            n/=10;
        }
    }

    最后是如何解决问题的函数

    int slove(int n)//其实计算的是【0,n-1】的状态数,这一点PPT中讲到过。对于一个小于n的数,肯定是从高位到低位出现某一位<n的那一位。
    
    {
    int sum=0;//最终的状态数之和 int len=cntlen(n); cal(n,len); //这一部分很关键   for(int i=len;i>=1;i--)//因为数组是倒序保存的,相当于从原来数的个位开始,直到最高位 { for(int j=0;j<digit[i];j++)//这里的 j 模拟的是当前位置的取值,肯定不能等于digit[i]。 //因也是因为ppt中讲过的性质:对于一个小于n的数,肯定是从高位到低位出现某一位<n的那一位。 { if(!(digit[i+1]==6&&j==2)&&j!=4) { sum+=dp[i][j];//符合条件的累加到sum中 } } //下面这个if的位置千万不能放在前面,原因我在下面说   if(digit[i]==4||(digit[i+1]==6&&digit[i]==2))//当遇到当前位是4或是当前位是2并且下一位是6,就没有必要再进行下去。 //因为往下走,前面遍历过的位置是不会变的,已经出现了62或者4,后面无论怎么变化都是不合规定的。 { break; } } return sum; }

    第一:为什么上面代码中的 j 取值必须小于digit[i]?

    因为开始表述的性质,我们知道一段区间内符合要求的数是不会等于它本身的。

    举例:给出一个数字:123624

    先是最高位1的取值,它可以取0。然后开始计算他的状态。他的最大值是0,然后假设后面位置都最大可以取9,得到的数字就是099999。这个数字是小于100000的。

    然后跳到下一位2的时候,最高位固定为1不变,就不再需要考虑。接着再考虑下一位。

    实际上可以把数字进行拆分:123624=100000+20000+3000+600+20+4

    100000之内计算的是【100000,199999】区间的状态

    20000之内计算的是  【10000,19999】区间的状态

    3000之内计算的是    【1000,2999】区间的状态

    600之内计算的是      【100,599】区间的状态

    20之内计算的是        【10,19】区间的状态

    4之内计算的是          【0,3】区间的状态

    前面每一位少的都会在下一位补充进去,但是最后4没有进行补充。合并整个区间就得到的是【0,123623】的状态。


    第二:为什么if必须放在后面不放在前面?

    因为 j 的取值是小于当前位digit[i]的。

    还是上面的例子,123624,倒序放置在数组中就成为了426321。

    依照程序从最低位4开始,取值0~3。这个是合法的。但如果把if放在前面,这部分的状态就没有加上去,最终结果就会变小。

    当遇到当前位是4或是当前位是2并且下一位是6,就没有必要再进行下去。因为往下走,前面遍历过的位置是不会变的,后面无论怎么变化都是不合规定的。


    代码:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<string>
    #include<algorithm>
    using namespace std;
    int dp[8][10];
    int digit[10];
    void init()
    {
        memset(dp,0,sizeof(dp));
        dp[0][0]=1;
        for(int i=1;i<=7;i++)
        {
            for(int j=0;j<=9;j++)
            {
                for(int k=0;k<=9;k++)
                {
                    if(j!=4&&!(j==6&&k==2))
                    {
                        dp[i][j]+=dp[i-1][k];
                    }
                }
            }
        }
    }
    int cntlen(int n)
    {
        int len=0;
        while(n)
        {
            len++;
            n/=10;
        }
        return len;
    }
    void cal(int n, int len)
    {
        memset(digit,0,sizeof(digit));
        for(int i=1;i<=len;i++)
        {
            digit[i]=n%10;
            n/=10;
        }
    }
    int slove(int n)
    {
        int sum=0;
        int len=cntlen(n);
        cal(n,len);
        for(int i=len;i>=1;i--)
        {
            for(int j=0;j<digit[i];j++)
            {
                if(!(digit[i+1]==6&&j==2)&&j!=4)
                {
                    sum+=dp[i][j];
                }
            }
            if(digit[i]==4||(digit[i+1]==6&&digit[i]==2))
            {
                break;
            }
        }
        return sum;
    }
    int main()
    {
        int n,m;
        init();
        while(~scanf("%d%d",&n,&m))
        {
            if((n==0)&&(m==0))
            {
                break;
            }
            printf("%d
    ",slove(m)-slove(n-1));
        }
        return 0;
    }
    


  • 相关阅读:
    集合的遍历和扩容原理
    最新鲜最详细的Android SDK下载安装及配置教程
    开启IIS Express可以调试X64项目
    C# 正则表达式大全
    Windows下SQLMAP的安装图解
    当前比较流行的前端框架2
    前端组件库1
    服务器Windows 登录 出现401 错误
    IPV6配置
    implicit和 explicit关键字
  • 原文地址:https://www.cnblogs.com/lemonbiscuit/p/7776038.html
Copyright © 2011-2022 走看看