hdu3555 Bomb 【数位dp+记忆化dfs】

题目链接:hdu3555 Bomb

题意:
t组样例,给正整数n (n<=2631) ,输出[1, n]中包含”49”的数的个数。


1. 传统dp
dp预处理出 [0,10],[0,100]...[0,1020] 的答案,根据n每位的值按情况累加即可;
注意n包括49的情况, 例如n=234950这种情况, 49之后不再按每位值累加,而是[234900, 234950]都符合。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;

typedef long long LL;

int a[25];
LL dp[25][3];
//dp[i][0] len <= i && 包含 49 的个数
//dp[i][1] len = i && 首位是 9 && 不包含 49 的个数
//dp[i][2] len <= i && 不包含 49 的个数
void init()
{
    memset(dp, 0, sizeof(dp));
    dp[0][2] = 1;
    for(int i = 1; i < 22; i++)
    {
        //转移方程
        dp[i][0] = 10 * dp[i-1][0] + dp[i-1][1];
        dp[i][1] = dp[i-1][2];
        dp[i][2] = 10 * dp[i-1][2] - dp[i-1][1];
    }
}
LL sol(LL x)
{
    int w = 0;
    x++;                //答案没有算自身,统计的是(1, n)开区间,可以变为统计(1,n+1); 或者返回答案时特判+1。
    while(x)
    {
        a[++w] = x % 10;
        x /= 10;
    }
    a[w+1] = 0;

    LL ans = 0;
    int flag = false;                   //flag 表示前几位中是否存在49

    for(int i = w; i > 0; i--)
    {
        ans += a[i] * dp[i-1][0];        
        if(flag)                       
        //前几位存在49时 剩余位全都符合 ans += a[i]*1e(i-1)  
        //因为1e(i-1)可以拆成dp[i-1][0]+dp[i-1][2], 只需再加上dp[i-1][2]
            ans += a[i] * dp[i-1][2];    
        else                           
        //不存在49时 0~a[i]-1已考虑 即为a[i]*dp[i-1][0],只需再考虑a[i]的情况
        {
            if(a[i] > 4)      //a[i]>4时 i位可以为4和i-1位为9且不含49的情况组合 一共dp[i-1][1]个
                ans += dp[i-1][1];
            if(a[i+1] == 4 && a[i] == 9)  //出现49 标记
                flag = true;
        }
    }
    //if(flag) ans++;
    return ans;
}
int main(int argc, char const *argv[])
{
    init();
    int t;
    scanf("%d", &t);
    while(t--)
    {
        LL n;
        scanf("%lld", &n);
        printf("%lld\n", sol(n));
    }
    return 0;
}

2. 记忆化dfs
刚学的记忆化搜索写法,看了好久才理解,注意dp数组意义与传统dp中的不相同。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

typedef long long LL;

int bit[25];
LL dp[25][3];
//第一维对应st = 0, 1, 2 三种情况
//第二维对应位数

LL dfs(int pos, int st, bool flag)
//pos:当前位数
//st: 高位状态      st=0: 没有49; st=1: 前一位为4; st=2: 前几位中有49。
//flag:高位是否不是原数
{
    if(pos == 0)
        return st == 2;
    if(flag && dp[pos][st] != -1)
        return dp[pos][st];

    LL ans = 0;
    int x = flag? 9: bit[pos];

    for(int i = 0; i <= x; i++)
    {
        if(st == 2 || st == 1 && i == 9)
            ans += dfs(pos-1, 2, flag || i < x);
        else if(i == 4)
            ans += dfs(pos-1, 1, flag || i < x);
        else
            ans += dfs(pos-1, 0, flag || i < x);
    }

    if(flag)
        dp[pos][st] = ans;

    return ans;
}

LL sol(LL x)
{
    int len = 0;
    while(x)
    {
        bit[++len] = x % 10;
        x /= 10;
    }
    return dfs(len, 0, false);
}
int main(int argc, char const *argv[])
{
    int t;
    memset(dp, -1, sizeof(dp));
    scanf("%d", &t);
    while(t--)
    {
        LL x;
        scanf("%lld", &x);
        printf("%lld\n", sol(x));
    }
    return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值