HDU - 2089 不要62(数位dp,通过这个最基础的题来随便谈谈数位dp)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2089

题目意思:给定区间没有62和4的数有多少个

思路:

数位dp包括数位和dp。数位就是通过把数分解成一位一位的数来分析。那么数位dp的dp用来记忆什么东西呢?

在一个区间中(比如【1,100000】),找没有62和4的数,一个一个检算很显然会有很多重复的部分

(比如【101,110】和【1001,1010】这两个区间中满足条件的个数都是9,而且抽象来讲这两个区间很相似,仔细想想哪里相似)。那么就可以很快求出这个区间中你想要的数的个数。

现在来讲讲哪里相似(就这道题来说),这两个区间末尾都是1到10,那么对于最后一位数(个位)他们前面一位数(十位)都是相同的。所以,如果都属于同一位数(比如最后一位),且前一位数都相同,那么就可以直接用,这就是dp的两维了。

现在还有最后一个问题,就是什么情况下可以用这个dp(太懒了,从大佬博客扣了下面这段):

       ********

控制上界枚举,从最高位开始往下枚举,例如:ri=213,那么我们从百位开始枚举:百位可能的情况有0,1,2(觉得这里枚举0有问题的继续看)

然后每一位枚举都不能让枚举的这个数超过上界213(下界就是0或者1,这个次要),当百位枚举了1,那么十位枚举就是从0到9,因为百位1已经比上界2小了,后面数位枚举什么都不可能超过上界。所以问题就在于:当高位枚举刚好达到上界是,那么紧接着的一位枚举就有上界限制了。具体的这里如果百位枚举了2,那么十位的枚举情况就是0到1,如果前两位枚举了21,最后一位之是0到3(这一点正好对于代码模板里的一个变量limit 专门用来判断枚举范围)。

相信读者还对这个有不少疑问,笔者认为有必要讲一下记忆化为什么是if(!limit)才行,大致就是说有无limit会出现状态冲突,举例:

约束:数位上不能出现连续的两个1(11、112、211都是不合法的)

假设就是[1,210]这个区间的个数

状态:dp[pos][pre]:当前枚举到pos位,前面一位枚举的是pre(更加前面的位已经合法了),的个数(我的pos从0开始)

先看错误的方法计数,就是不判limit就是直接记忆化

那么假设我们第一次枚举了百位是0,显然后面的枚举limit=false,也就是数位上0到9的枚举,然后当我十位枚举了1,此时考虑dp[0][1],就是枚举到个位,前一位是1的个数,显然dp[0][1]=9;(个位只有是1的时候是不满足的),这个状态记录下来,继续dfs,一直到百位枚举了2,十位枚举了1,显然此时递归到了pos=0,pre=1的层,而dp[0][1]的状态已经有了即dp[pos][pre]!=-1;此时程序直接return dp[0][1]了,然而显然是错的,因为此时是有limit的个位只能枚举0,根本没有9个数,这就是状态冲突了。有lead的时候可能出现冲突,这只是两个最基本的不同的题目可能还要加限制,反正宗旨都是让dp状态唯一

*******

所以,只有当此时状态没有限制(limit)时才能使用dp来记忆循环使用。

好了,下面贴一下这个题的代码:

#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define eps 1e-8
#define lson l, m, rt<<1
#define rson m+1, r, rt<<1|1
using namespace std;
typedef long long LL;
typedef pair<int, int> pii;
const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;

int l,r;
int a[20];
int dp[20][20];
int dfs(int pos,int sta,bool limit){
    if (pos==-1) return 1;
    if (!limit&&dp[pos][sta]!=-1) return dp[pos][sta];
    int up=limit?a[pos]:9;
    int tmp=0;
    for (int i=0;i<=up;i++){
        if (i==4) continue;
        if (sta==6&&i==2) continue;
        tmp+=dfs(pos-1,i,a[pos]==i&&limit);
    }
    if (!limit) dp[pos][sta]=tmp;
    return tmp;
}
int solve(int x){
    int pos=0;
    while (x){
        a[pos++]=x%10;
        x/=10;
    }
    return dfs(pos-1,0,1);
}
int main() {
    while (~scanf ("%d%d",&l,&r)&&l&&r){
        memset(dp,-1,sizeof(dp));
        printf ("%d\n",solve(r)-solve(l-1));
    }
    return 0;
}


猜你喜欢

转载自blog.csdn.net/LLL_yx/article/details/81241529
今日推荐