数位DP UVA - 11038

数位DP,顾名思义,是在个位,十位,百位,千位…….这些数的数位上进行的DP,它其实就是一种暴力枚举+记忆化搜索。
数位DP一般用来解决要求找出某个区间内,满足要求的数有多少个之类的问题,在这里我拿UVA - 11038来举例。这道题就是给两个整数,然后要你求出这两个整数之间的数有多少个0,比如说1到10之间有1个0,100到110之间有12个0。这是一道最基础的数位DP题,我们可以令f(a)为从0到a有多少个0,那么这道题的结果就可以表示为f(r)-f(l-1),然后我们就只需要求f(a),我们先从最高位开始枚举,比如说f(415),那么百位可以是0,1,2,3,4,当百位是0和4的时候都需要特殊考虑,如果百位是0那就表示我们目前枚举的是一个两位数,有前导零,我们可以定义一个bool lead来判断,如果百位是4,那么我们的十位数只能取0,1,因为不能超过上限,但如果百位数不是4,那么我们的十位数就可以取0到9中的任意一个,我们可以定义一个bool limit来判断。因为我们是按位枚举,所以说我们需要定义一个int pos表示当前处理的位数,有了这三个变量我们还差最后一个,也就是DP题所特有的状态变量,不同题目的状态变量是不一样的,这题因为要计算0的个数,我们可以定义状态变量为int state表示从最高位数到当前位数为止有多少个非前导0,有了这四个变量我们就可以开始写数位DP啦,每一个数位DP都需要这四个变量,状态变量在不同的题目里是不一样的,但前三个基本不会变。
下面这个模板我借鉴了WH dalao的,做了许多小改动。

typedef long long LL;  
int a[20];  
LL dp[20][state];//不同题目状态不同    
LL dfs(int pos,/*state变量*/, bool lead/*前导零*/, bool limit/*数位上界变量*/)//不是每个题都要判断前导零    
{  
    //递归边界,既然是按位枚举,最低位是1,那么pos==0说明这个数我枚举完了    
    if (pos == 0) return 1;/*这里一般返回1,表示你枚举的这个数是合法的,那么这里就需要你在枚举时必须每一位都要满足题目条件,也就是说当前枚举到pos位,一定要保证前面已经枚举的数位是合法的。不过具体题目不同或者写法不同的话不一定要返回1 */  
                           //第二个就是记忆化(在此前可能不同题目还能有一些剪枝)    
    if (!limit && !lead && dp[pos][state] != -1) return dp[pos][state];  
    //常规写法都是在没有限制的条件记忆化,这里与下面记录状态是对应的。   
    int up = limit ? a[pos] : 9;//根据limit判断枚举的上界up   
    LL ans = 0;  
    //开始计数    
    for (int i = 0; i <= up; i++)//枚举,然后把不同情况的个数加到ans就可以了    
    {  
        if () ...  //根据题目要求分不同情况继续进行dfs  
        else if ()...  
            ans += dfs(pos - 1,/*状态转移*/, lead && i == 0, limit && i == a[pos]) //最后两个变量传参都是这样写的    
    }  
    //计算完,记录状态    
    if (!limit && !lead) dp[pos][state] = ans;  
    /*这里对应上面的记忆化,在一定条件下时记录,保证一致性,当然如果约束条件不需要考虑lead,这里就是lead就完全不用考虑了*/  
    return ans;  
}  
LL solve(LL x)  
{  
    int pos = 1;  
    while (x)//把数位都分解出来    
    {  
        a[pos++] = x % 10;  
        x /= 10;  
    }  
    return dfs(pos - 1/*从最高位开始枚举*/,/*初始状态*/, true, true);//刚开始最高位都是有限制并且有前导零的,显然比最高位还要高的一位视为0嘛    
}  
int main()  
{  
    LL l, r;  
    memset(dp, -1, sizeof(dp));  
    while (~scanf("%lld%lld", &l, &r))  
    {  
        //初始化dp数组为-1  
        printf("%lld\n",solve(r)-solve(l-1));   
    }   
}  

这个模板可以适用于许多数位DP的题目,但都要根据题目要求进行一些修改与补充,比如说这道题套用这个模板的话代码如下。

#include <iostream>  
#include <cstdio>  
#include <cstring>  
#include <cstdlib>  
#include <cmath>  
#include <vector>  
#include <queue>  
#include <map>  
#include <algorithm>  
#include <set>  
#include <functional>  
using namespace std;  
typedef long long LL;  
typedef unsigned long long ULL;  
const int INF = 1e9 + 5;  
const int MAXN = 50007;  
const int MOD = 1e8 + 7;  
const double eps = 1e-7;  

int a[50];  
LL dp[50][50];  

LL dfs(int pos, int state, bool lead, bool limit)  
{  
    if (pos == 1)//当还剩最后一位的时候  
        if (lead)//如果有前导零,那就只有0这一个数  
            return 1;  
        else//如果没有前导零  
            if (limit)//如果最后一位数有上限,当最后一位数为0的时候有1+state个0,不为0的时候有state*a[pos]个0  
                return 1 + state + state*a[pos];  
            else//如果没有上限,那么0到9一共10个state,再加上为0的时候的1个。  
                return 1 + state * 10;  
    if (!limit && !lead&&dp[pos][state] != -1)  
        return dp[pos][state];  
    int up = limit ? a[pos] : 9;  
    LL ans = 0;  
    for (int i = 0; i <= up; i++)  
    {  
        if (i==0&&lead)//如果有前导0的话,当前的这个0不会加入state中  
        {  
            ans += dfs(pos - 1, state, lead && i == 0, limit && i == a[pos]);  
            continue;  
        }  
        if (i == 0)//如果没有前导0的话,当前的这个0就会加入state中  
            ans += dfs(pos - 1, state + 1, lead && i == 0, limit && i == a[pos]);  
        else  
            ans += dfs(pos - 1, state, lead && i == 0, limit && i == a[pos]);  
    }  
    if (!limit && !lead)  
        dp[pos][state] = ans;  
    return ans;  
}  

LL solve(LL x)  
{  
    int pos = 1;  
    while (x)  
    {  
        a[pos++] = x % 10;  
        x /= 10;  
    }  
    return dfs(pos - 1,0 , true, true);//初始的非前导0一个也没有,所以是0  
}  

int main()  
{  
    LL l, r;  
    LL a, b;  
    memset(dp, -1, sizeof(dp));  
    while (scanf("%lld%lld",&l,&r)!=EOF)  
    {  
        if (l == -1 && r == -1)  
            break;  
        if (r == 0)//0需要特判一下  
            a = 1;  
        else  
            a = solve(r);  
        if (l == 0)  
            b = 0;  
        else if (l == 1)//因为l要减一,但题目里l可以是0和1,所以在这里0和1都特判一下  
            b = 1;  
        else  
            b = solve(l - 1);  
        printf("%lld\n", a-b);  
    }  
}  
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值