zoukankan      html  css  js  c++  java
  • 数位dp真·浅谈 By cellur925

    预警:由于是从$Vergil$学长那里和$Mathison$大神那里学来的,所以清一色记忆化搜索!qwq

    巨佬的数位dp讲解(未来的咕咕日报头条):

    https://www.luogu.org/blog/virus2017/shuweidp


    数位dp嘛,顾名思义...就是与每个数上的那位有关系...(废话)

    一般问题形式都是,在区间$[l,r]$中,满足xx条件的数有多少。然后数据范围一般巨大,枚举是不可能的,这辈子是不可能的。所以一般我们都把它当做字符串处理,预处理出每一位的数(数位),即之后的$border$数组。先求出1~r内满足范围的数,在求出1~l-1范围内的数,运用前缀和的思想一减的。

    关于处理成字符串:

    一种高精处理方法

            scanf("%s",p+1);
            int lenp=strlen(p+1);
            
            int last=lenp;
            while(p[last]=='0'&&last) last--;
            for(int i=last+1;i<=lenp;i++) p[i]='9';
            p[last]--;
            
            for(int i=1;i<=lenp;i++) a[lenp-i+1]=p[i]-'0';
            memset(dp,-1,sizeof(dp));
            ans1=dfs(lenp,0,1);
            
            scanf("%s",q+1);
            int lenq=strlen(q+1);
            for(int i=1;i<=lenq;i++) a[lenq-i+1]=q[i]-'0';
            memset(dp,-1,sizeof(dp));
            ans2=dfs(lenq,0,1);
            printf("%d
    ",ans2-ans1);

    另一种暴力搞,longlong范围内才可。

            l--;
            while(l)
            {
                border[++len]=l%10;
                l/=10;    
            }
            memset(dp,-1,sizeof(dp));
            ans1=dfs(len,0,1);
            len=0;
            
            while(r)
            {
                border[++len]=r%10;
                r/=10;
            }
            memset(dp,-1,sizeof(dp));
            ans2=dfs(len,0,1);
            len=0;
            printf("%lld
    ",ans2-ans1);

    记忆化搜索?其实感觉他是dp的本质吧...(说错了不要打我啊qwq)。搜索我们要遍历所有状态,遍历完就溜,啥也不记下来,没心没肺;dp我们也要遍历全部状态,但是长心眼了,懂得存起来,用空间换时间;然后dp是和记忆化搜索学的,所以记忆化搜索也记录了搜过的状态,当现在搜的之前搜过,就直接调用之前的结果。Update:Vergil学长表示他认为dp的本质是最短路 把各状态当作图的节点,然后与开始节点连的边为初始值...


    既然我们记搜了,那么肯定我们需要知道每次记录哪些量:一般地,我们会记到$pos$(现在在第几位,个人习惯从右往左为第1位到第$len$位)、$pre$(上一位是哪个数字,用于与上一位有关的情况)、$ling$前导零标记、$limit$最高位标记、$st$是否已经满足要求了,还有其他实际情况需要的。开始从主程序进来的时候,$ling$和$limit$都是1。

    图自@Mathison真·大神

    另外,当$limit=1$不能记录和调用dp值!当$ling=1$也不能调用记录dp值!


    以上是概论,现在分两种题型扯几道例题。

    第一种题型:问你数位满足是否有xx数 or 没有xx数

    • 例1 hdu2089不要62

       题目大意:问你$[l,r]$中不含4且不含连续62的数的个数。

       真·入门题。因为不含,所以只要有4或62连续,之后我们就不用搜了。

     1 #include<cstdio>
     2 #include<algorithm>
     3 #include<cstring>
     4 
     5 using namespace std;
     6 typedef long long ll;
     7 
     8 int l,r,len;
     9 int border[100];
    10 ll ans1,ans2,dp[10][12];
    11 
    12 ll dfs(int pos,int pre,int limit)
    13 {
    14     if(pos<1) return 1;
    15     if(!limit && dp[pos][pre]!=-1) return dp[pos][pre];
    16     ll tmp=0;
    17     ll lim=limit ? border[pos] : 9;
    18     for(int i=0;i<=lim;i++)
    19     {
    20         if(i==4) continue;
    21         if(i==2&&pre==6) continue;
    22         tmp+=dfs(pos-1,i,(limit&&i==lim));
    23     }
    24     if(!limit) dp[pos][pre]=tmp;
    25     return tmp;
    26 }
    27 
    28 int main()
    29 {
    30     while(scanf("%d%d",&l,&r)!=EOF&&l!=0&&r!=0)
    31     {
    32         l--;
    33         while(l)
    34         {
    35             border[++len]=l%10;
    36             l/=10;    
    37         }
    38         memset(dp,-1,sizeof(dp));
    39         ans1=dfs(len,0,1);
    40         len=0;
    41         
    42         while(r)
    43         {
    44             border[++len]=r%10;
    45             r/=10;
    46         }
    47         memset(dp,-1,sizeof(dp));
    48         ans2=dfs(len,0,1);
    49         len=0;
    50         printf("%lld
    ",ans2-ans1);
    51     }
    52     return 0;
    53 }
    View Code
    • 例2 hdu3555Bomb

       题目大意:问你1~n中含连续49的数的个数。

       数位dp入门题。因为题目问的是“含”,所以我们必须记一个$st$,看之前是否满足。这里就不能像上一道题一样不满足直接$continue$掉了。因为之后还有希望满足啊qwq。

     1 //求1~n 含有连续49的数字有多少
     2 #include<cstdio>
     3 #include<algorithm>
     4 #include<cstring>
     5 
     6 using namespace std;
     7 typedef long long ll;
     8 
     9 int T,len;
    10 int border[200];
    11 ll dp[200][13][5];
    12 char sta[200];
    13 
    14 ll dfs(int pos,int pre,int st,int limit)
    15 {
    16     if(pos<1) return st;
    17     if(!limit && dp[pos][pre][st]!=-1) return dp[pos][pre][st];
    18     ll tmp=0;
    19     ll lim=limit ? border[pos] : 9;
    20     for(int i=0;i<=lim;i++)
    21         tmp+=dfs(pos-1,i,st||(i==9&&pre==4),limit&&i==lim);
    22     if(!limit) dp[pos][pre][st]=tmp;
    23     return tmp;
    24 }
    25 
    26 int main()
    27 {
    28     scanf("%d",&T);
    29     while(T--)
    30     {
    31         scanf("%s",sta+1);
    32         len=strlen(sta+1);
    33         for(int i=1;i<=len;i++)
    34             border[i]=sta[len-i+1]-'0';
    35         memset(dp,-1,sizeof(dp));
    36         printf("%lld
    ",dfs(len,0,0,1));
    37     }
    38     return 0;
    39 } 
    View Code
    • 例3 poj3208 Apocalypse Someday

    第2种题型:问你数位是否满足一些比较复杂的要求(就不是上面那么简单了)

     例题会尽量都补上的qwq

    关于输出方案:本来想试着输出下方案的,后来发现实际输出的时候其实会少很多。因为我们记忆化!当一个状态下的值已经确定了,我们就不再切身实际地搜,而是直接调用之前结果了,也就方案会少输出了。当然,没记忆化的时候,显然我们可以输出全部方案。

    数位dp。。。按Mathison大佬的话,还是非常套路模板化的,唯一要注意的是举一反三和前导零的处理...。

  • 相关阅读:
    性能测试总结(三)--工具选型篇
    什么是性能瓶颈
    基于Linux服务器的性能分析与优化
    常见的APP性能测试指标
    性能测试方案设计的方法和思路
    测试渐进式
    Selenium2Library关键字
    互联网运营工作四大内容
    RF学习过程中遇到的问题
    Robot Framework自动化测试环境的搭建
  • 原文地址:https://www.cnblogs.com/nopartyfoucaodong/p/9787923.html
Copyright © 2011-2022 走看看