zoukankan      html  css  js  c++  java
  • dp:数位dp

    方法一:

    对每一位数字为某一个值时的情况分步计算

    难度:低

    实用性:低,仅在要求判断数位中是否出现某个数字时可用

     

    例如求数字中没有连续62且没有4的情况

    456小的数,可以这么考虑,

    4          5             6

    4        5           (0~6)

    4        (0~4)        (0~9)

    (0~3)   (0~9)        (0~9)

    先计算百位为0,1,2,3时能有多少个数满足(0~399)

    然后计算十位为0,1,2,3,4时能有多少个数满足(000~049)

    最后计算十位为0~6时能有多少个数满足(000~006)

    这里说明一下0~49等价于400~449

    假如这些数字的前面出现了4或62,则这些数字不计

    没有出现,则前面的数字的值没有影响

     

    讲解一下实际操作

    dp[][],第一维表示位数(个,十,百等),第二维表示该位上的数字是几

    先赋初值,显然个位上除4以外每一个的值都为1

     

    然后状态转移方程为

    dp[i][j]=dp[i-1][0]+ dp[i-1][1]+…+dp[i-1][9]

    然后要加入62和4的判断

    FOR(i,2,6)

           {

                  FOR(j,0,9)

                  {

                         if(j==4) continue;

                         FOR(k,0,9)

                         {

                                if(k==4||(j==6&&k==2)) continue;

                                dp[i][j]+=dp[i-1][k];

                         }

                  }

           }     

    其中j的位数在k的左边,用于判断62是否存在

     

    初始化是比较简单的,但在求解过程中中有很多易错点

    先把读入的数字每一位都排出来

           while(k)

           {

                  a[++cnt]=k%10;

                  k/=10;

           }

           a[cnt+1]=0;

    这里一定要保证数组最大位的前一位为0,因为后续判断中会用到这一位

    首先要从高位到低位

    比如456,我们先算了0~399,由于有4,后面的数字都是非法的

    所以高位的数字决定低位的数字是否有效

    所以出现这些数字时即可结束循环

     

    假如百位上是3,我们要计算0~299,所以我们实际上计算了

    0~99

    100~199

    200~299

    也就是比3小的数字,因此循环的范围为0~(a[i]-1)

    这个计算方式在计算最后一位时是会少算一个数字的

    比如123,我们实际上算了0~122,所以要算的数字还要自增1

    ROF(i,cnt,1)

           {

                  FOR(j,0,a[i]-1)

                  {

                         if((a[i+1]==4&&j==9)) continue;

                         ans+=dp[i][j];

                  }

                  if((a[i+1]==4&&a[i]==9)) break;

           }

    以上方法是非常基础的,书写易出错且用处非常有限

     

     

    方法二:

    记忆化搜索dp

    难度:高

    实用性:高,几乎一切情况

     

    这里先补充一下dp的认知

    以数塔为例

    如果你枚举所有的状态,会出现很多非常相似的状态,它们之间只有很小的区别,而且你会进行大量的重复运算,如果找到某个规律能够利用所有被计算过的状态,并运用到将要进行的运算中,效率将会大大提升。

    然而数塔的情况非常单调,当你推理状态时,状态仅用一个一维数组就能完全表示。但即便如此,我们依旧可以进行记忆化搜索。

    记忆化搜索即———在判断过该状态后,下次再遇到时,直接借用过去的结果。

     

    代码:

    int dfs(int x,int y) 

           if(dp[x][y]) 

           return dp[x][y];

           if(x>n||y>n) return 0;

           dp[x][y]=max(dfs(x+1,y),dfs(x+1,y+1))+a[x][y]; 

           return dp[x][y]; 

    }

    那么dp[1][1]就是我们需要的答案,而且其运算顺序与常规的状态方程写法一致。

    因此,我们可以发现,dp和记忆化搜索有着极其密切的联系,如果目前看不懂数位dp的搜索,不如先用记忆化搜索重写以前的dp题加深理解。

     

    以下给出记忆化搜索的数位dp的一个实例

    ll dfs(int pos,int sta,int limit)

    //3到4四个参数,为当前位,状态(可能用两个数表示状态),和上限

    {

           //cout<<pos<<" "<<sta<<" "<<f[sta]<<endl;

           if(pos==0)//到最后一位数字,判断该数字是否满足条件

           {

                  return f[sta]==1;

           }

           if(!limit&&dp[pos][sta]!=-1)

      //在位数相同,状态相同时,直接借用过去结果,limit是是否达到上限值,比如上次算出的是1000到1999,与现在2000到2999刚好同一情况,但上限(初始输入)小于2999,所以不能借用

           {

                  return dp[pos][sta];

           }     

           int up=limit?a[pos]:9,s,g=sta,k=sta;

           ll ans=0;

           FOR(i,0,up)

           {

                  if(i==0)

                  {

                         if(sta!=0)

                         {

                                s=g%3;

                                g/=3;

                                if(s==0||s==1) k=sta+t[i];

                                else k=sta-t[i];

                         }

                  }

                  else

                  {

                         s=g%3;

                         g/=3;

                         if(s==0||s==1) k=sta+t[i];

                         else k=sta-t[i];

                  }

                  ans+=dfs(pos-1,k,limit&&i==up);

           }

           if(!limit)//没达到上限时,记录该状态

                  dp[pos][sta]=ans;

           return ans;

    }

    ll solve(ll x)

    {

        int pos=0;

        while(x)

        {

            a[++pos]=x%10;

            x/=10;

        }

        a[pos+1]=0;

        return dfs(pos,0,1);

    }

    题目对应SPOJ BALNUM ,实际做题按需求改写,没有什么泛用性,但结构基本一致。

    顺带先讲一下这道题,题目要求在l和r中有多少BALNUM,BALNUM是指,数位中的奇数出现偶数次,偶数出现奇数次,没有出现的不考虑,如1,111,11111,22,2222都是BALNUM。

    用三进制表示0到9的状态,0为没出现,1为奇数次,2为偶数次。

    由于只有1和2加入计算,所以可以通过以下二进制处理出所有符合条件的状态数字

    注:t[j]为3的j次方

    FOR(i,1,pow(2,10)-1)

           {

                  int l=i,ret=0;

                  FOR(j,0,9)

                  {

                         if(l&1) ret+=((j%2)==1?2*t[j]:t[j]);

                         l>>=1;

                  }

                  //cout<<i<<" "<<ret<<endl;

                  f[ret]=1;

           }

     就不放完整代码了,用时为0ms

  • 相关阅读:
    106. Construct Binary Tree from Inorder and Postorder Traversal
    105. Construct Binary Tree from Preorder and Inorder Traversal
    449. Serialize and Deserialize BST
    114. Flatten Binary Tree to Linked List
    199. Binary Tree Right Side View
    173. Binary Search Tree Iterator
    98. Validate Binary Search Tree
    965. Univalued Binary Tree
    589. N-ary Tree Preorder Traversal
    eclipse设置总结
  • 原文地址:https://www.cnblogs.com/qq936584671/p/8987539.html
Copyright © 2011-2022 走看看