数位DP _ HDU 2089

数位DP:用来解决一个区间内符号条件的数字个数,并且这个需要满足的条件与数字大小没有什么关系,只和数字的组成有一定的关系,比如求解[1,10000]中不含数字4的个数,等等

数位DP实质上就是对数字的每个位置上进行枚举判断,但是在这个过程中dp会记录下之前的状态,以便后面会遇到和当前状态相同的状态,比如[0,213]中,找到所有不含数字4的个数,那么状态一般对于一个数字当百位十位分别为00和百位十位01的状态就是相同的,因为他们只是十位的数字不同,但在个位上他们的满足条件的个数一定是相同的,再比如百位为0,和百位为1的状态还是相同的,因为十位和个位上满足条件的数字也一定是相同的.......

那么我们都是由高位向低位进行枚举,然后再这个过程中不断的进行状态的转移,同时我们还有一个限制变量,表示当前位置上是否有限制,比如213,当百位为2,枚举十位那么这个枚举就是有限制的、当百位十位为21,枚举个位也是有限制的、但是百位为0或者1,后面的状态都不会有限制

题中让在一个区间中找出数字中不含4并且不含62的个数,那么这个题中我们就还有另外一个限制条件,当前一位为6时,后面一个位置不能为2,所以我们在每次搜索的时候,我们要记录一下上一位的数字是什么,并且当上一位是6的时候,这时我们储存的状态也不相同,但是除了6之外的状态都是相同的,那么我们就可以将dp分为两个状态,一个为上一位数字是6、另一个是上一位数字不止6的,那么我们使用下面状态方程表示:

我们用dp[i][j]表示:在当前枚举的位置i处,(j==0,当前i位置不是6,j == 1,当前位置为6)后面所有所有满足条件的总和

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <math.h>
#include <queue>
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
int a[20];
int dp[20][2];//dp[pos][sta]表示当前第pos位,前一位是否是6的状态,这里sta只需要去0和1两种状态就可以了,不是6的情况可视为同种,不会影响计数。

int dfs(int pos, int pre,int sta, bool limit)
/**
pos   当前处理的位置,从高位到低枚举!  最大就是数字的长度!
pre   上一位的数字    -1表示不存在
sta   要到达的状态,如果为1则可以认为找到答案了,到时候用来返回,给计数器+1
limit 是否有限制,即当前的处理这一位能否随便取值,比如567、当前处理6这一位,如果前面一位是4,那么当前为随便取,如果前面一位为5,那么当前这位就不能随便取了,不然会超出这个数的范围!
**/
{
    if(pos == -1) return 1;//递归边界,既然是按位枚举,已经都枚举到最后一位了,说明这个数已经枚举完了

    if(!limit && dp[pos][sta] != -1) return dp[pos][sta];     ///状态的储存,比如000的最后一位状态已经写好了,那么我们在更新010的个位状态时(没有限制的情况下),可以直接使用以前的状态更新!
    int up = limit?a[pos]:9;//如果该为有限制,最大为9,否则最大为a[pos]
    int tmp = 0;
    for(int i = 0;i <= up;i ++)//枚举下一个位置的数字
    {
        if(pre == 6 && i == 2) continue;
        if(i == 4) continue;//两种限制条件!
        tmp += dfs(pos-1,i,i==6,limit && i == a[pos]);//这里判断的一种情况为6,因为6的时候我们还要判断后面一位一定不能为2,所以和前面储存的状态不能连用!
        //下一位限制是否存在必须为当前这一位限制存在,并且上一位限制也是存在的,
    }
    if(!limit) dp[pos][sta] = tmp;//必须保证这个位置上没有限制,我们才可以更新这一个选项,因为只有没有限制下,某个状态下的数字才是固定的!
    return tmp;
}

int Solve(int x)
{
    int pos = 0;
    while(x)
    {
        a[pos++] = x%10;//数组里面储存   数字从低位高位依次的数字
        x/=10;
    }
    return dfs(pos-1,-1,0,true);
}
int main()
{
    int le,ri;
    while(~scanf("%d%d",&le,&ri) && le+ri)
    {
        memset(dp,-1,sizeof(dp));
        printf("%d\n",Solve(ri) - Solve(le-1));
    }
    return 0;
}

    虽然程序很简短,但是数位DP的这个思想是真的很巧妙,并且也不是很好理解这个程序,日后刷题的时候,再慢慢加深这方面的理解,因为上面说的十分潦草,有很多大犇写的博客会解释的更清楚一些,所以大家可以看一下参考博客中一些写的很好的博客

部分优化

数位DP的题目中一般和给的数据没有关系,只是和每个数字的组成有关系,所以一般情况下,我们的dp数组是不用每次都去更改为-1的,因为找出数字不含4和62,那么这时这个数的性质,那么和我么的输入根本就没有关系,所以我们可以这样写main函数

int main()
{
    int le,ri;
    memset(dp,-1,sizeof(dp));
    while(~scanf("%d%d",&le,&ri) && le+ri)
    {
        printf("%d\n",Solve(ri) - Solve(le-1));
    }
    return 0;
}

参考博客

模板:https://blog.csdn.net/wust_zzwh/article/details/52100392

https://www.cnblogs.com/zbtrs/p/6106783.html

猜你喜欢

转载自blog.csdn.net/li1615882553/article/details/80201579