浅谈数位DP

数位DP,从入门到放弃

PS:这篇博客主要是浅谈数位DP,但是博主不能保证看一遍都能看懂,建议多读几遍(最好多换几台电脑进行阅读)

从一道例题讲起:

例1.

给出整数m、n, 求m ~ n 中不含子串‘62’和‘4’的整数的个数,比如621就不满足,612满足,124不满足。

想法:

这道题我们首先可以想到暴力, 从m到n循环,如果满足条件,则ans++, 这个想法很好,也很好实现, 但是看一下一般的题的数据范围:

【约束条件】1 ≤ a ≤ b ≤ 10^18

这时,循环显得也很费时间,可以先预处理一遍,打表,我计算了一下,也就是18626451492G这么大的文件,GoodLuck!

还有一种方法就是 数位DP
所谓数位DP,就是把一个十进制数的每一位拆开,在把每一位的信息作为状态来推出答案。
比如:12345这个数可拆为1、2、3、4、5。
推状态的方法有很多,我采用的是记忆化搜索(就是dfs时记录一个节点是否访问过来节省时间),我个人认为记忆化搜索是做数位DP的题的最简便、最通用的方法, 因为记忆化搜索代码短,便于理解,每道题只用把模板稍微改一下就能A题(至少我现在这样认为)

再回到例一,我们可以用dp[pos][pre]来保存在第pos位(就是刚刚提到的把数拆开),以pre结尾时满足题意的数的个数。
这个dp函数是解题的关键, 我就不在这里讲这么想出来的想出dp函数代表的意义以后,就可以套模板了。

解释模板:


int solve( int x ){
    int pos = 0;
    for (; x; a[pos] = x % 10, x /= 10, pos++); 
    return dfs( pos-1, 0, true);
}
solve函数主要作用是把每个数拆分开(如果看不懂for循环,建议学习一下后缀数组)

int dfs( int pos, int sta, bool lim ){
    if ( pos < 0 ) return /*大多为1*/;
    if ( !lim && dp[pos][/* 根据题意改变*/] != -1 ) return dp[pos][sta];
    int up = lim ? a[pos] : 9, ret = 0;
    for (register int i = 0; i <= up; i++ ){
        if ( sta && /*根据题意*/ ) continue;
        ret += dfs( pos-1, i == 6, lim && i == a[pos]);
    }
    if ( !lim ) dp[pos][sta] = ret;
    return ret;
}
dfs函数是最主要的函数,也是整个程序中代码量最大的函数,它的作用是求出答案。其中:
lim && i == a[pos]
这段代码是为确定下一个数位的上限作准备,据说初学者会觉得这有点难理解,我就重点解释一下。
假设上限是555
现在已经DP到了5xy了,如果x的值为5, 那y的上限就为5, 如果x为4那y的上限就为9,因为448,449都小于555

以上就是模板,我做的每道数位DP(水题)都以它为模板,下面我来讲一下做简单的数位DP题(我会在下文推荐)的基本套路:

1:确定DP函数,一般为dp[pos][…][…];

2 : 确定条件,比如上题pre!=6 || i != 2

3 : 套模板,对于求范围(m~n)的值可以用solve(n)-solve(m-1)来代替。


推荐做题顺序:

1.HDU2089[不要62]
题解
2.HDU3652[B-number]
题解
3.poj3252[Round Numbers]
题解
4.POJ4734[F(x)]
题解
5.BZOJ1833[count 数字计数]
题解
6.BZOJ1026[windy数]
题解
7.BZOJ1799 [self 同类分布]
题解

最后说一句:

有人给我泼脏水,给记忆化搜索泼脏水,他们说记忆化搜索不如预处理+递推优,我想说,一派胡言,我们速度比不上你们,我们代码量比你们棒


建议多刷题理解

如有不理解或者想喷人

可以在下面留言


  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值