• 数位DP入门


    切换至❤新页面,体验更加哦

    数位dp,是一种用来计数的dp

    如果现在给你一道题,需要你求在区间[l,r]内满足条件的解的个数,我们很容易想到去暴力枚举,但要是数据范围太大这种办法就行不通了,这时候数位dp就派上了用场,所谓数位就是把一个数拆成一个一个进制位,然后逐一比较看是否满足题目要求,这其实也是一种暴力方法,只不过时间复杂度小了很多

    那么到底要如何做呢?下面我们来看一道例题

    HDU2089

    概括一下题目意思

    就是给你一个区间[n,m],要你求区间内不含"62"或"4"的数字的个数,如8134(含4),21262455(含62)均不满足题意,而61342这种"6"和"2"并不连在一起的数字则满足题意

    直接统计对于暴力枚举很好求,但是对于数位dp并不容易,所以我们还需要用到差分的思想,即统计0到b+1(注意不是b,至于为什么后面会讲)和0到a的满足条件的个数,再两者相减

    进一步化简,求0到i位数不含4和62的个数

    i=1,求0~9的满足条件的个数

    i=2,求0~99的满足条件的个数

    i=3,求0~999的满足条件的个数

    i=4,求0~9999的满足条件的个数

    ...

    用dp[i][0]表示i位数中幸运数的个数

    用dp[i][1]表示i位数中以2开头的幸运数的个数

    用dp[i][2]表示i位数中非幸运数的个数

    那么,就有以下的递推公式

    dp[i][0]=dp[i-1][0]*9-dp[i-1][1]//表示前i-1位数字中的幸运数前面加上除4以外的0~9的其他数字,共9个,还要减去前i-1位数字中的以2开头的幸运数加上这一位以6开头的数字的个数

    dp[i][1]=dp[i-1][0]//表示前i-1位数字中的幸运数加上这一位的2

    dp[i][2]=dp[i-1][2]*10+dp[i-1][1]+dp[i-1][0]//表示前面已经不合法的数字这一位无论放什么都不合法,所以0~9随便放,加上前i-1位数字中的以2开头的幸运数加上这一位的6,再加上前i-1位数字中的幸运数加上这一位的4的个数

    初始值 dp[0][0]=1,其他均为0

    根据初始值和递推公式,我们就能得到从0到任意i位数字的吉利数字的个数。

    找到0 ~ n 的吉利数字的个数

    我们先求出0 ~ n 之间非吉利数字的个数,用总数减去即可。那,非吉利数字的个数怎么求呢?

    用具体的数字举例来说吧:设 n = 583626

    用digit[10]记录n+1每一位对应的数字,此例中有6位数字(令cnt = 6 表示数字位数),分别是

    digit[6] = 5

    digit[5] = 8

    digit[4] = 3

    digit[3] = 6

    digit[2] = 2

    digit[1] = 7

    digit[0] = 任意数字,占位用的

    用sum记录非吉利数字的个数,初始化为0

    需要一个bool量 flag,记录是否出现了非吉利数字。初始化为false, 未出现。

    我们从数字的最高位起进行判断:digit[6] = 5, 我们求 0 ~ 499999 之间非吉利数的个数。

      首先:加上0 ~ 99999中所有非吉利数字前面添加0~4的任意一个数字的情况 sum += dp[5][2] * digit[6]

      其次:5大于4,故我们要加上 0~99999中所有吉利数字前面添加4的情况 sum += dp[5][0]

    接着,判断第5位digit[5] = 8,即判断500000 ~ 579999 之间的非吉利数字的个数,其实就是判断0 ~ 79999之间的,前面的数字不是6就没有什么用

      首先:加上0 ~ 9999中所有非吉利数字前面添加0~7的任意一个数字的情况 sum += dp[4][2] * digit[5]

      其次:8大于4,故我们要加上 0~9999中所有吉利数字前面添加4的情况 sum += dp[4][0]

      此外:8大于6,故我们要加上0~9999中所有以2开头的吉利数字前添加6的情况 sum += dp[4][1]

    接着,判断第4位digit[4] = 3,即判断580000 ~ 582999 之间的非吉利数字的个数,其实就是判断0 ~ 2999之间的

      首先:加上0 ~ 999中所有非吉利数字前面添加0~2的任意一个数字的情况 sum += dp[3][2] * digit[4]

      其次:2小于4,没有需要特别考虑的

      此外:2小于6,没有需要特别考虑的

    接着,判断第3位digit[3] = 6,即判断583000 ~ 583599 之间的非吉利数字的个数,其实就是判断0 ~ 599之间的

      首先:加上0 ~ 99中所有非吉利数字前面添加0~5的任意一个数字的情况 sum += dp[2][2] * digit[3]

      其次:6大于4,故我们要加上 0~99中所有吉利数字前面添加4的情况 sum += dp[2][0]

    接着,判断第2位digit[2] = 2,即判断583600 ~ 583619 之间的非吉利数字的个数,其实就是判断0 ~ 19之间的,

      首先:加上0 ~ 9中所有非吉利数字前面添加0~1的任意一个数字的情况 sum += dp[1][2] * digit[2]

      其次:2小于4,没有需要特别考虑的

      此外:2小于6,没有需要特别考虑的

      但是,需要注意的是,这里判断的数字出现了62,我们要把flag标识为true。

    最后,判断第1位digit[1] = 7, 判断583620 ~ 583626但是这里flag为true了,表示前面的数字里面已经包含了非吉利数字,所以后面需要把所有的数字情况都加入到非吉利里面。(正是因为每次判断的数字末尾都比该位的数字少1,所以最开始要记录n + 1 的值)

    sum += digit[1] * dp[0][2] + digit[1] * dp[0][0]

     1 #include<bits/stdc++.h>
     2 #define in(i) (i=read())
     3 using namespace std;
     4 int read() {
     5     int ans=0,f=1; char i=getchar();
     6     while(i<'0'||i>'9') {if(i=='-') f=-1; i=getchar();}
     7     while(i>='0'&&i<='9') {ans=(ans<<3)+(ans<<1)+i-'0'; i=getchar();}
     8     return ans*f;
     9 }
    10 int dp[10][3],digit[15];
    11 void init() {
    12     memset(dp,0,sizeof(dp));
    13     dp[0][0]=1;
    14     for(int i=1;i<=8;i++) {
    15         dp[i][0]=dp[i-1][0]*9-dp[i-1][1];
    16         dp[i][1]=dp[i-1][0];
    17         dp[i][2]=dp[i-1][2]*10+dp[i-1][1]+dp[i-1][0];
    18     }
    19 }
    20 int solve(int x)
    21 {
    22     memset(digit,0,sizeof(digit));
    23     int cnt=0,tmp=x;
    24     while(tmp) {
    25         digit[++cnt]=tmp%10;
    26         tmp/=10;
    27     }
    28     digit[cnt+1]=0; int flag=0,ans=0;
    29     for(int i=cnt;i>=1;i--) {
    30         ans+=digit[i]*dp[i-1][2];
    31         if(flag) ans+=digit[i]*dp[i-1][0];
    32         else {
    33             if(digit[i]>4) ans+=dp[i-1][0];
    34             if(digit[i]>6) ans+=dp[i-1][1];
    35             if(digit[i+1]==6 && digit[i]>2) ans+=dp[i][1];
    36         }
    37         if(digit[i]==4 || (digit[i+1]==6 && digit[i]==2)) flag=1;
    38     }
    39     return x-ans;
    40 }
    41 int main()
    42 {
    43     int a,b; init();
    44     while(1) {
    45         in(a);in(b);
    46         if(!a && !b)  break;
    47         cout<<solve(b+1)-solve(a)<<endl;
    48     }
    49     return 0;
    50 }


    最后说那个b+1的情况,我们看到代码中有判断digit[i]>4和digit[i]>6等类似的语句,我们处理第i位时,实际上是处理0~digit[i]-1,即[ 0,digit[i] ),而把digit[i]放到下一次去判断,但我们处理个位时,最后一个是不会去统计的,所以我们把统计的范围+1,即为[ 0,digit[i]+1 )-->[ 0,digit[i] ],所以就有了solve(b+1)-solve(a)这样的语句.(还是高二dalaoNavi-Awson告诉我的,%%%)

    数位dp记忆化搜索写法

     1 #include<bits/stdc++.h>
     2 #define in(i) (i=read())
     3 using namespace std;
     4 typedef long long lol;
     5 lol read() {
     6     lol ans=0,f=1;    
     7     char i=getchar();
     8     while(i<'0'|| i>'9') {if(i=='-') f=-1; i=getchar();}
     9     while(i>='0' && i<='9') {ans=(ans<<1)+(ans<<3)+i-'0'; i=getchar();}
    10     return ans*f;
    11 }
    12 lol a,b;
    13 lol dp[19][11];
    14 lol bit[19];
    15 lol dfs(lol len,lol pre,lol limit) {
    16     if(!len) return 1;//如果搜到最后一位了,返回下界值1
    17     if(!limit && dp[len][pre]) return dp[len][pre];//记忆化部分
    18     lol maxn=limit?bit[len]:9;//求出最高可以枚举到哪个数字
    19     lol ans=0;
    20     for(lol i=0;i<=maxn;i++) {
    21         if(i!=4 && !(pre && i==2))//如果这一位不为4并且上一位不为6且这一位不为2
    22             ans+=dfs(len-1,i==6,limit && i==maxn);//满足条件
    23     }
    24     if(!limit) dp[len][pre]=ans;//如果没有限制,代表搜满了,可以记忆化,否则就不能
    25     return ans;
    26 }
    27 lol solve(lol a) {
    28     memset(bit,0,sizeof(bit));
    29     lol k=0;
    30     while(a) {//取出数字的每一位
    31         bit[++k]=a%10;
    32         a/=10;
    33     }
    34     return dfs(k,0,1);
    35 }
    36 int main()
    37 {
    38     //freopen("number.in","r",stdin);
    39     //freopen("number.out","w",stdout);
    40     lol t; in(t);
    41     for(int i=1;i<=t;i++) {
    42         lol a,b; in(a);in(b);
    43         cout<<solve(b)-solve(a-1)<<endl;//差分思想
    44     }
    45     return 0;
    46 }

    应用

    放几道题目上来

    [SCOI2009]windy数 代码

    [HNOI2010]计数 

    [ZJOI2010]数字计数 题解

  • 相关阅读:
    强制表格内容不换行
    数组深度
    JDBC连接SQLService时报错误:“驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接"
    Excel中神奇的vlookup函数之基础应用
    利用python进行泰坦尼克生存预测——数据探索分析
    pandas数据处理基础——基础加减乘除的运算规则
    pandas数据处理基础——筛选指定行或者指定列的数据
    python读取文本文件数据
    服务器硬件基础知识
    WordPress实现伪静态
  • 原文地址:https://www.cnblogs.com/real-l/p/8540124.html
Copyright © 2020-2023  润新知