数位dp-基础

一、数位dp?干嘛的?

通常用来求两个数之间符合条件的数的个数,而且这两个数很大,暴力做不来,数位dp一般用来计数,因为数位dp是按位来计算,所以大数都搞得定,数的大小对时间复杂度的影响很小,而且一般符合某种条件的这个条件与数的组成有关,与大小无关

二、如何设计状态??

据我所知,数位dp一般有两种实现方式,一是预处理+乱搞,二是记忆化搜索,相比之下,记忆化搜索更好理解也更模板化,所以我们就用第二种来实现一下吧

从起点向下搜索,到最底层得到方案数,一层一层向上返回答案并累加,最后从搜索起点得到最终答案。对于 [l,r] 区间问题,利用前缀和的思想求出 [0,r] 和 [0,l-1] ,相减即可

那我们dfs要传什么状态进去呢?

(一般是)基本的当前位数pos,最高位限制limit(判断前导零的lead,一些其他的状态,与前几位的关系,等等)除此之外还可以传进更多参量以区分状态

1) 前导零lead

比如POJ - 3252 Round Numbers 这个题,最高位就是不允许有前导零的(这个0影响了我们要求的状态的数字的结构,所以要记,不影响的时候是不需要记的,记了也没毛病),我们需要记录当前的数是不是前导零,如果是的话,就不要算到结果里

2)最高位限制limit

我们在搜索过程中,搜的范围可能会发生变化,比如说我想找0-312中间有多少个含7的数,0-100,101-200,201-300范围都是相同的,十位都是从0搜到9,但从300开始就不能搜到9了,300-312的十位应该从0搜到1,这时范围就变化了,而且都是边界的下一个边界才有限制,我们只记录边界就可以了,312,百位的3是边界limit=1,0 1 2 的limit都=0,只有31的1limit=1,其余为0,就这样子

若当前位 limit=1 而且已经取到了能取到的最高位时,下一位 limit=1 ;

若当前位 limit=1 但是没有取到能取到的最高位时,下一位 limit=0 ;

若当前位 limit=0 时,下一位 limit=0

下一位的limit就是limit&&i==a[pos](这位能取到的最大值)

3)dp数组下标记录的值

既然从高位往低位枚举,那么状态一般都是与前面已经枚举的数位有关并且通常是根据约束条件当前枚举的这一位能使得状态完整(比如一个状态涉及到连续k位,那么就保存前k-1的状态,当前枚举的第k个是个恰好凑成成一个完整的状态,不过像那种状态是数位的和就直接保存前缀和为状态了),不过必然有一位最简单的一个状态dp[pos]当前枚举到了pos位

数位dp是在记忆化搜索的框架下进行的,每当找到一种情况我们就可以这种情况记录下来,等到搜到后面遇到相同的情况时直接使用当前记录的值,减少不必要的操作

dp数组的下标表示的是一种状态,只要当前的状态和之前搜过的某个状态完全一样,我们就可以直接返回原来已经记录下来的dp值

以上就是基础的数位dp的过程,如果遇到什么要补充的,再来填这个坑~o.o~

模板:

 1 int a[20];
 2 ll dp[20][state];//不同题目状态不同
 3 ll dfs(int pos,/*state变量*/,bool lead/*前导零*/,bool limit/*数位上界变量*/)//不是每个题都要判断前导零
 4 {
 5     //首先就是记忆化(在此前可能不同题目还能有一些剪枝)
 6     if(!limit && !lead && dp[pos][state]!=-1) return dp[pos][state];
 7     /*常规写法都是在没有限制的条件记忆化,这里与下面记录状态是对应*/
 8 
 9     //递归边界,既然是按位枚举,最低位是0,那么pos==-1说明这个数我枚举完了
10     if(pos==0) return 1;/*这里一般返回1,表示你枚举的这个数是合法的,那么这里就需要你在枚举时必须每一位都要满足题目条件,也就是说当前枚举到pos位,一定要保证前面已经枚举的数位是合法的。不过具体题目不同或者写法不同的话不一定要返回1 */
11 
12     int up=limit?a[pos]:9;//根据limit判断枚举的上界up;
13     ll ans=0;
14     //开始计数
15     for(int i=0;i<=up;i++)//枚举,然后把不同情况的个数加到ans就可以了
16     {
17         if() ...
18         else if()...
19         ans+=dfs(pos-1,/*状态转移*/,lead && i==0,limit && i==a[pos]) //最后两个变量传参都是这样写的
20         /*这里还算比较灵活,不过做几个题就觉得这里也是套路了
21         大概就是说,我当前数位枚举的数是i,然后根据题目的约束条件分类讨论
22         去计算不同情况下的个数,还有要根据state变量来保证i的合法性*/
23     }
24     //计算完,记录状态
25     if(!limit && !lead) dp[pos][state]=ans;
26     /*这里对应上面的记忆化,在一定条件下时记录,保证一致性,当然如果约束条件不需要考虑lead,这里就是lead就完全不用考虑了*/
27     return ans;
28 }
29 ll solve(ll x)
30 {
31     int pos=0;
32     while(x)//把数位都分解出来
33     {
34         a[++pos]=x%10;//个人老是喜欢编号为[1,pos],看不惯的就按自己习惯来,反正注意数位边界就行
35         x/=10;
36     }
37     return dfs(pos/*从最高位开始枚举*/,/*一系列状态 */,true,true);//刚开始最高位都是有限制并且有前导零的,显然比最高位还要高的一位视为0嘛
38 }
39 int main()
40 {
41     memset(dp,-1,sizeof dp);//放到外面,不影响答案正确性,优化时间
42     ll le,ri;
43     while(~scanf("%lld%lld",&le,&ri))
44     {
45         printf("%lld\n",solve(ri)-solve(le-1));
46     }
47 }

QAQ 模板从肖天顺学长那里搞的

猜你喜欢

转载自www.cnblogs.com/YangKun-/p/12633016.html