zoukankan      html  css  js  c++  java
  • 数位dp 模板加例题

    概念:所谓数位”dp“,是指对数字的”位“进行的与计数有关的DP。一个数一个位,十位,百位,千位等,数的每一位就是数位。数位DP用来解决与数字操作有关的问题。例如数位之和的问题。特定数字问题等。这些问题的特征就是给定的区间不能超级大,不用用暴力的方法逐个检查,必须接近O(log2n) 复杂度的算法。解题的思路是用DP对”数位“进行操作,记录已经算过的区间状态,用在后续计算中,快速进行大范围的筛选。

    实现方法:1.递推实现 2.用记忆法搜索实现。

    一.递推的实现:

        我们来看一到例题:统计[0,n]内不含4的数字个数(直接上代码)

      

    #include<cstdio>
    #include<algorithm>
    #include<set>
    #include<iostream>
    using namespace std;
    const int maxn=2e5+5;
    const int M=15;  
    int dp[M][M];  //dp[i][j]表示i位数字第一个数是j是符合条件的数字数量;
    int bit[M];    //bit[i]存第i位数字;
    void init()
    {
      dp[0][0]=1;
      for(int i=1;i<=M;i++)
      {
        for(int j=0;j<10;j++)
        {
          for(int k=0;k<10;k++)
          {
            if(j!=4) dp[i][j]+=dp[i-1][k];
          }
        }
      }
    }
    //计算[0,n]区间内满足条件数字的个数;
    int solve(int len)
    {
      int ans=0;
      for(int i=len;i>=1;i--)  //从高位到地位处理;
      {
        for(int j=0;j<bit[i];j++)
           if(j!=4) ans+=dp[i][j];
         if(bit[i]==4) {ans--;break;}  //i位开头是4的都不行;
      }
      return ans;
    }
    int ok(int x)  //简单的一个求bit数组以及n的位数;
    {
      int len=0;
      while(x)
      {
        bit[++len]=x%10;
        x/=10;
      }
      return solve(len);
    }
    int main()
    {
      int n;
      init();  //预计算dp[][];
      cin>>n;
      printf("%d
    ",ok(n)+1); //不要忘记0也是符合条件;
      return 0;
    }

      二.记忆化搜索:

     记忆化搜索的思路其实是在递归程序dfs中搜索所以可能的情况,但是遇到已经算过的记录在dp[ ]中的结果就可以直接使用,减少了重复计算。然后记忆化搜索是有模板(这让我这种dp菜鸡有一丝丝动力)

    const int M=15;  
    int dp[M][sta];  //sta对应不同转态;
    int bit[M];    
    int dfs(int pos,/*sta变量*/,int lead/*前导零*/,int lim/*数位上界变量*/)
    {
      if(pos==-1) return 1; //判断是否枚举到最低位;或者有些题目用于剪枝;
    
      if(!lim&&dp[pos][sta]!=-1) return dp[pos][sta];
    
      int up=lim?bit[pos]:9;
      
      int ans=0; //计数;
      for(int i=0;i<=up;i++)
      {
        if()...
        else if()...
        //不同题目的要求不同;
        //这里是记忆化搜索最灵活的部分要具体题目分析;
    
        ans+=dfs(pos-1,/*sta状态转移*/,lead&&i==0,lim&&i==bit[pos]);
      }
      if(!lim) dp[pos][sta]=ans;
      return ans;
    }
    int solve(int x)
    {
      int  len=0;
      while(x)
        {
          bit[len++]=x%10;
          x/=10;
        }
        return dfs(len-1,/*从最高位开始枚举sta状态*/,/*一系列状态*/,1);
    }
    int main()
    {
      int l,r;
      while(~scanf("%d%d",&n,&m))
      {
        memset(dp,-1,sizeof dp); //dp数组初始化,这个也可以放在while外面;
        printf("%d
    ",solve(r)-solve(l-1));
      }
      return 0;
    }

     看了模板我们来看几题例题:

     1.Hdu 2089 题意是统计[n,m]内不含4且没有“62”的数:

     代码如下:

    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    #include<iostream>
    #include<string>
    using namespace std;
    typedef unsigned long long ull;
    const int maxn=4e2+5;
    const int Inf=0x3f3f3f3f;
    int dp[15][15];
    int bit[15];
    int dfs(int pos,int pre,int sta,int lim) //pre记录前一个数字;
    {
        if(pos<=-1) return 1;  
        if(!lim&&dp[pos][sta]!=-1) return dp[pos][sta];
        int up=lim?bit[pos]:9;
        int ans=0;
        for(int i=0;i<=up;i++)
        {
            if(pre==6&&i==2) continue; //如果前一个数字是六现在补上的是2那么跳过循环;
            if(i==4) continue;      
            ans+=dfs(pos-1,i,i==6,lim&&i==bit[pos]);
        }
        if(!lim)  dp[pos][sta]=ans;
        return ans;
    }
    int solve(int x)
    {
        int pos=0;
        while(x)
        {
            bit[pos++]=x%10;
            x/=10;
        }
      return dfs(pos-1,0,0,1);
    }
    int main()
    {   
        int l,r;
        memset(dp,-1,sizeof dp);
        while(~scanf("%d%d",&l,&r)&&r+l)
        {
            printf("%d
    ",solve(r)-solve(l-1));
        }
       return 0;
    }
    

      2.Hdu 3652 题意是求[1,n]中能被13整除且包含“13”的数。这题较上题多一个整除的判断处理需要多开一维来记录余数:

     代码如下:

    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    #include<iostream>
    #include<string>
    using namespace std;
    typedef unsigned long long ull;
    const int maxn=1e5+5;
    int dp[15][15][3];
    //dp[i][j][k]
    //i:数位
    //j:余数
    //k:3种操作状况,0:末尾不是1 , 1:末尾是 1 ,2:含有13
    
    int bit[15];
    int dfs(int pos,int mod,int have,int lim)
    {
        if(pos<=0) return mod==0&&have==2;    //枚举完成返回合理的数字即 能被13整除且包含“13”;
        if(!lim&&dp[pos][mod][have]!=-1) return dp[pos][mod][have];
        int up=lim?bit[pos]:9;
        int mod_x,have_x;
        int ans=0;
        for(int i=0;i<=up;i++)
        {
            mod_x=(mod*10+i)%13;  //其实是模拟除法;
            have_x=have;
            if(have==0&&i==1)    //末尾数字是不是1现加入1,末尾为1;
               have_x=1;
            if(have==1&&i!=1)    //末尾是1 现加入的不为1 末尾不是1;
              have_x=0;
            if(have==1&&i==3)    //包含“13” 标记为2;
               have_x=2;
            ans+=dfs(pos-1,mod_x,have_x,lim&&i==bit[pos]);   
        }
        if(!lim) dp[pos][mod][have]=ans;
        return ans;
    }
    int solve(int x)
    {
        int len=0;
        while(x)
        {
            bit[++len]=x%10;
            x/=10;
        }
        return dfs(len,0,0,1);
    }
    int main()
    {
        int n; 
        memset(dp,-1,sizeof dp);
        while(~scanf("%d",&n)) cout<<solve(n)<<endl;
        return 0;
    }

    这两题是比较基础的数位dp,主要用于熟悉记忆化搜索的模板(hdu 3555类似于HDU2089 大家可以试试)

    比较难的有 hdu 6148,hdu 4507大家可以挑战挑战。

    个人感觉数位dp比一般的dp更容易理解,大家多花点时间效果是很明显的。

  • 相关阅读:
    c++ 单步查看汇编代码【转】
    c++ 类内部函数调用虚函数
    grep和sed替换文件中的字符串【转】
    vim 正则替换【转】
    linux 文件编码问题
    shell截取字符串的一些简单方法
    chrome 安装页面编码选择插件
    namespace main
    【转】c++ 多线程
    使用git提交到github,每次都要输入用户名和密码的解决方法
  • 原文地址:https://www.cnblogs.com/zpj61/p/13435924.html
Copyright © 2011-2022 走看看