hdu2089 不要62(数位dp两种做法)

用几天的空余时间看了数位dp,其实光论思想还是比较简单的。


这题可以用暴力打表做,但是1000000的数据并不适用于所有题,太容易超时了。

数位dp先初始化一个二维dp数组,行数代表该数要处理多少位,最高位放在最下面, 该行中每一项代表取0~9中的所有数后该属性数的数量。其中列数中dp[i][0]代表加上当前最高位后吉利数的数量,dp[i][2]代表加上当前最高位为2的情况下吉利数的数量(属于dp[i][0]的一部分),dp[i][2]代表加上当前最高位后不吉利数的数量,其中dp[i][0]加上dp[i][2]为加上该最高位的所有数的数量。


表格初始化完后,就得到每一位上不管为吉利还是不吉利数的个数,共6(位)行3(状态)列。


然后是处理,都写注释了。教程:点击打开链接


#include <stdio.h>
#include <algorithm>
#include <string.h>

using namespace std;

const int N = 10;
const int INF = 1e8;

int dp[N][5], bit[N];

void init()
{
    memset(dp, 0, sizeof(dp));
    dp[0][0] = 1;
    for(int i = 1; i <= 6; i ++)
    {
        dp[i][0] += dp[i - 1][0] * 9 - dp[i - 1][1];//在上一位的基础上乘9(除了4,因为乘4为不吉利),然后减去当前位为6前一位为2的状况(注意不一定是一种)
        dp[i][1] += dp[i - 1][0];//当前位为2的吉利数数量,便于后续处理
        dp[i][2] += dp[i - 1][2] * 10 + dp[i - 1][0] + dp[i - 1][1];//在上一位基础上乘10(前驱为不吉利这一位就随便了),再加上上一位为吉利当前为4的状况(变为不吉利),当前位为6上一位为2的状况(变为不吉利)
    }
}

int solve(int n)
{
    int len = 0, ans = 0, flag = 0, tmp = n;
    while(n)
    {
        bit[++ len] = n % 10;
        n /= 10;
    }
    bit[len + 1] = 0;
    for(int i = len; i > 0; i --)
    {
        ans += dp[i - 1][2] * bit[i];//ans代表不吉利数个数,首先加上前驱为不吉利,注意处理具体数时要有大小限制,比如是5就乘以5,代表1、2、3、4、5的5种情况。注意不能为0,最高位为0代表该位不存在。
        if(flag) ans += dp[i - 1][0] * bit[i];//如果被标记,则已经为不吉利数,就把上一位的吉利数都变为不吉利的基础上乘以本位种类
        if(!flag && bit[i] > 4) ans += dp[i - 1][0];//当前位大于4,说明会处理等于4的情况,把上一位吉利数变为不吉利(本位1种情况)
        if(!flag && bit[i] > 6) ans += dp[i - 1][1];//<span style="font-family: Arial, Helvetica, sans-serif;">当前位大于6,说明会处理等于6的情况,把上一位为2的吉利数变为不吉利(本位1种情况)</span>
        if(!flag && bit[i] > 2 && bit[i + 1] == 6) ans += dp[i][1];//<span style="font-family: Arial, Helvetica, sans-serif;">当前位大于2,说明会处理等于2的情况,且下一位等于6(注意是等于才行),把当前为2的吉利数变为不吉利(本位1种情况)</span>
        if(bit[i] == 4 || bit[i] == 2 && bit[i + 1] == 6) flag = 1;//标记
    }
    return tmp - ans;
}

int main()
{
   //  freopen("in.txt", "r", stdin);
    int l, r;
    while(~scanf("%d%d", &l, &r) && (l + r))
    {
        init();
        printf("%d\n", solve(r + 1) - solve(l));
    }
    return 0;
}


接下来是记忆化搜索法。记忆化搜索要思想就是以搜索为主,dp过程中把中间结果保存,下次访问时若此项访问过则直接返回,节省大量时间。

这思想用来处理数位dp刚好合适,处理数字时高一位总是在低一位的基础上得出,如果每一位都要对后面所有位都处理,那未免太操蛋了。


至于细节,注意不同于上一种做法,这里是当一棵树遍历到底层后,只有是不吉利的数字才会返回,所以返回在存储的时候实际上存储到当前位的都是上一位的不吉利数字。


#include <stdio.h>
#include <algorithm>
#include <string.h>

using namespace std;

typedef long long LL;

const int N = 21;
const int INF = 1e8;

int dp[N][5], bit[N];

int dfs(int pos, int pre, bool limit, bool flag)
{
    if(pos == 0) return flag;
    if(!limit && flag && dp[pos][0] != -1) return dp[pos][0];
    if(!limit && !flag && pre != 6 && dp[pos][2] != -1) return dp[pos][2];
    if(!limit && !flag && pre == 6 && dp[pos][1] != -1) return dp[pos][1];
    int endd = limit ? bit[pos] : 9;
    int ans = 0;
    for(int i = 0; i <= endd; i ++)
    {
        ans += dfs(pos - 1, i, limit && (i == endd), flag || (pre == 6 && i == 2) || (i == 4));
    }
    if(!limit)
    {
        if(flag) dp[pos][0] = ans;
        if(!flag && pre != 6) dp[pos][2] = ans;
        if(!flag && pre == 6) dp[pos][1] = ans;
    }
    return ans;
}

int solve(int n)
{
    int len = 0;
    while(n)
    {
        bit[++ len] = n % 10;
        n /= 10;
    }
    return dfs(len, 0, true, false);
}

int main()
{
  //  freopen("in.txt", "r", stdin);
    int l, r;
    while(~scanf("%d%d", &l, &r) && (l + r))
    {
        memset(dp, -1, sizeof(dp));
        printf("%d\n", r - l + 1 - (solve(r) - solve(l - 1)));
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值