HDU2089不要62数位DP入门

前言

上学期差不多整个学期都在打各种比赛,几乎没学新算法,因此寒假好好补一波算法,不过数位DP入个门也花了我两天,学习效率还没回来… …

模板题

题目链接

给定一个数字区间[l,r],问你区间内有多少数不含4,也不含连续的62。

题目范围是1e7,可以On预处理+前缀和O1查询,代码就不放了。

数位DP

今天的重点是数位DP。

数位DP的好处在于直接对位进行暴力,另外加上记忆化,可以大大减少时间。例如枚举千位时,一次枚举就相当于暴力下的一千次枚举。

初学数位DP,我感觉他的本质就是记忆化搜索。因此先上无记忆化的搜索代码:

#include <bits/stdc++.h>
using namespace std;

int a[20], pos;

int dfs(int pos, int sta, int lim) //枚举到第pos位,上一位是否是6,该位是否有约束
{
    if (!pos)
        return 1;
    int sum = 0, up = lim ? a[pos] : 9;
    for (int i = 0; i <= up; i++)
    {
        if (sta && i == 2)
            continue;
        if (i == 4)
            continue;
        sum += dfs(pos - 1, i == 6, lim && i == up);
    }
    return sum;
}

int solve(int mx)
{
    pos = 0;
    while (mx)
    {
        a[++pos] = mx % 10;
        mx /= 10;
    }
    return dfs(pos, 0, 1);
}
int main()
{
    int l, r;
    while (cin >> l >> r && l + r)
        cout << solve(r) - solve(l - 1) << endl;
    return 0;
}

上面这份代码的写法其实比较奇怪,算是半个数位DP的写法吧。其中的lim是数位dp中一个关键点,可以看底下的参考博客理解。

这份代码本质上就是暴力,如果查询次数多或者数据范围加到1e9以上肯定是会TLE的,但是加上记忆化就不一样了,记忆化可以剪掉一大部分递归栈。记忆化只多了四五行代码:

/*
 * @Author: hesorchen
 * @Date: 2020-12-30 16:53:18
 * @LastEditTime: 2021-01-12 21:17:10
 * @Description: 栽种绝处的花
 */
#include <bits/stdc++.h>
using namespace std;

int a[20], pos;
int dp[20][2];

int dfs(int pos, int sta, int lim) //枚举到第pos位,上一位是否是6,该位是否有约束
{
    if (!pos)
        return 1;
    if (!lim && dp[pos][sta] != -1) //记忆化
        return dp[pos][sta];
    int sum = 0, up = lim ? a[pos] : 9;
    for (int i = 0; i <= up; i++)
    {
        if (sta && i == 2)
            continue;
        if (i == 4)
            continue;
        sum += dfs(pos - 1, i == 6, lim && i == up);
    }
    if (!lim) //记忆化
        dp[pos][sta] = sum;
    return sum;
}

int solve(int mx)
{
    memset(dp, -1, sizeof dp);
    pos = 0;
    while (mx)
    {
        a[++pos] = mx % 10;
        mx /= 10;
    }
    return dfs(pos, 0, 1);
}
int main()
{
    int l, r;
    while (cin >> l >> r && l + r)
        cout << solve(r) - solve(l - 1) << endl;
    return 0;
}

这里的记忆化需要判断!limit,这是一个稍难的点,事实上,就是为了避免limit或者非limit两种状态的冲突,经过实测,我们完全可以记忆化中的一种,不过就此题来说,显然!limit的状态更多,因此选择记忆这种效率更高。具体理解看底下的参考博客吧。

参考博客

  1. 数位dp总结 之 从入门到模板
上一篇:剑指 Offer 62. 圆圈中最后剩下的数字 + 约瑟夫环问题


下一篇:Ubuntu20.04 Linux 5.4.0-62 安装Cuda10.2