• ☆ [HDU2089] 不要62「数位DP」


    类型:数位DP

    传送门:>Here<

    题意:问区间$[n,m]$的数字中,不含4以及62的数字总数

    解题思路

    数位DP入门题

    先考虑一般的暴力做法,整个区间扫一遍,判断每个数是否合法并累计答案。而数位DP则认为可以换一种方法来枚举,找到对于一个数的上限,然后在这个限度内枚举每一个数位来统计答案

    为了方便数位DP,题意可以转化求区间$[0, k]$的符合要求的数字总数,因此答案就是$ans(M)-ans(N-1)$

    首先我们可以预处理出dp数组:$dp[i][j]$表示以$j$开头的$i$位数的符合要求的数字总数;例如,$dp[2][3]$表示以3开头的2位数中符合要求的,也就是区间$[30, 39]$中符合要求的。$$dp[i][j] = sumlimits_{0 leq k leq 9 \ k eq 4}dp[i-1][k]$$这个方程很好理解,相当于枚举一个数位塞到前面,同时需要保证不能把6塞当2前面,并且特判一下4就好了

    至于统计答案,我们从上限的最高位开始往下扫描。这里有个很巧妙的思想——每次处理不超过当前这一位的部分。形象地说,对于数字$21358$,最高位扫描$0~1$,也就是把答案累积上$dp[5][0~1]$。这一步相当于处理了区间$[0, 19999]$中的所有;此时默认最高位是2,扫描到下一位,累积$dp[4][0~0]$,也就相当于处理了区间$[20000,20999]$;依次类推,然后将会处理$[21000,21299]$,$[21300,21349]$,$[21350,21357]$。因此我们可以在$O(lg N * 10)$的复杂度内处理区间$[0, N-1]$。(注意不包括N)$$ans = sumlimits_{i=num}^{1}sumlimits_{j=0}^{digit[i]-1}dp[i][j]$$

    然后在来看判断62和4的问题:每当我们进入到下一位,我们就将默认上一位确定。此时若确定的那一位为4,那么之后的都不用考虑了(一定不合法)。同理,如果当前确定的为2且上一位确定的为6,那么也可以跳出。事实上,这个跳出不是优化,而是必须那么做——如果不跳出,就会错误地累积很多答案。同时,不仅进入下一位的时候要判断,扫描的时候也要判断。道理一样

    拓展:如果题目要求的不是【不要62】而是【要62】呢?就好像[HDU3555] Bomb所要求的一样,只需要求出所有的【不要62】数字,用N减一下就好了

    Code

    特别需要注意的是$digit[num+1]==0$这一步的处理,如果不加这一步,那么如果在处理前一个数字时残留下了$digit[num+1]==6$,那么你的程序将不能在最高位填充2了!

    另外还有dp数组的初始化问题:一种是$dp[0][0]=1$,或者对于所有$i eq 4$,$dp[1][i]=1$。其实这两者是等效的,因为在统计$dp[1][i]$时,只会累积到$dp[0][0]$为1

    /*By DennyQi 2018.8.13*/
    #include <cstdio>
    #include <queue>
    #include <cstring>
    #include <algorithm>
    #define  r  read()
    #define  Max(a,b)  (((a)>(b)) ? (a) : (b))
    #define  Min(a,b)  (((a)<(b)) ? (a) : (b))
    using namespace std;
    typedef long long ll;
    const int MAXN = 10010;
    const int MAXM = 27010;
    const int INF = 1061109567;
    inline int read(){
        int x = 0; int w = 1; register int c = getchar();
        while(c ^ '-' && (c < '0' || c > '9')) c = getchar();
        if(c == '-') w = -1, c = getchar();
        while(c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar(); return x * w;
    }
    int N,M,num,X,Y;
    int dp[10][11],digit[10];
    inline void Init(){
        dp[0][0] = 1;
        for(int i = 1; i <= 7; ++i){
            for(int j = 0; j <= 9; ++j){
                if(j == 4) continue;
                for(int k = 0; k <= 9; ++k){
                    if(k == 2 && j == 6) continue;
                    dp[i][j] += dp[i-1][k];
                }
            }
        }
    }
    inline int cul(int x){
        num = 0;
        int y = x, res = 0;
        while(y > 0){
            digit[++num] = y % 10;
            y /= 10;
        }
        digit[num+1] = -1;
        for(int i = num; i >= 1; --i){
            for(int j = 0; j < digit[i]; ++j){
                if(digit[i+1] == 6 && j == 2) continue;
                res += dp[i][j];
            }
            if(digit[i] == 4 || (digit[i+1]==6&&digit[i]==2)) break;
        }
        return res;
    }
    int main(){
        Init();
        for(;;){
            N = r, M = r;
            if(!N && !M) break;
            printf("%d
    ",cul(M+1)-cul(N));
        }
        return 0;
    }
  • 相关阅读:
    Nginx使用
    nginx常见配置详解
    配置yum源
    nginx常见使用方式和日志功能
    SpringCloud学习篇《一》
    myeclipse的各种背景:黑色,护眼,欢迎围观
    java基础二 <流程控制语句, 方法,数组,java内存结构> 未完待续...
    fastjson解析超长json串以及转成list,map等方法实例
    Linux下权限的修改-JDK的配置-文件的常见操作
    java面试基础大全,绝对经典<126-170><转>
  • 原文地址:https://www.cnblogs.com/qixingzhi/p/9466388.html
Copyright © 2020-2023  润新知