数位DP

Posted Achen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数位DP相关的知识,希望对你有一定的参考价值。

刚学DP的时候觉得数位DP非常难,后来发现还是套路比较深。

1.

BZOJ1833 count 数字计数.cpp

写的第一个,非递归版。感觉非递归的数位Dp比较巧妙,也还是比较套路 

先预处理,然后统计比最高位位数小的树的答案,统计最高位位数的答案时,先统计不到lim的,再往下一层统计lim的

 

技术分享
//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
typedef long long LL;
int lim[20];
LL l,r,power=1,dp[20][20][20],ans[2][10];
void pre(){
    for(int i=0;i<10;i++)
    dp[1][i][i]=1;
    for(int i=2;i<=13;i++){
      power*=10;
      for(int j=0;j<10;j++){
          dp[i][j][j]+=power;
          for(int k=0;k<10;k++)
           for(int l=0;l<10;l++)
            dp[i][j][l]+=dp[i-1][k][l];
      }
    }
}
void calu(LL x,int p){
    int k=0;
    if(!x) lim[++k]=0;
    while(x){
       lim[++k]=x%10;
       x/=10; 
    }
    LL power=1,cnt=1;
    for(int i=1;i<=k;i++){
        ans[p][lim[i]]+=cnt;
        cnt+=lim[i]*power;
        power*=10;
        for(int j=(i==k&&i!=1);j<lim[i];j++)
            for(int kk=0;kk<10;kk++)
                ans[p][kk]+=dp[i][j][kk];
        for(int j=(i!=2);j<10;j++) 
            for(int kk=0;kk<10;kk++)
                ans[p][kk]+=dp[i-1][j][kk];
    }
      
}
int main()
{
    pre();
    scanf("%lld%lld",&l,&r);
    if(l>=1) calu(l-1,0); 
    calu(r,1);
    for(int i=0;i<10;i++){
    if(i) putchar( );
    printf("%lld",ans[1][i]-ans[0][i]);
    }
    return 0;
}
BZOJ 1833 count 数字计数

 

2.BZOJ 1026 windy数

套路跟上一题差不多,几乎抄的学长的标程感觉比较坑,对1的处理有问题,但问题不大这道题数据可以过。

技术分享
//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
const int maxn=2000000000+299;
const int maxl=12;
int l,r,dp[maxl][20],base[maxl];
void pre(){
    for(int i=0;i<10;i++) dp[1][i]=1;
    for(int i=2;i<maxl;i++)
        for(int j=0;j<10;j++) 
            for(int k=0;k<10;k++)
                if(abs(j-k)>=2) dp[i][j]+=dp[i-1][k];
    base[1]=1; 
    for(int i=2;i<maxl;i++) base[i]=base[i-1]*10;
}
int cal(int x){
    int res=0;
    int w=10;
    if(!x) return 0;
    while(base[w]>x) w--;
    for(int i=1;i<w;i++)
    for(int j=1;j<10;j++)
    res+=dp[i][j];
    int cur,pre;
    pre=x/base[w];
    for(int i=1;i<pre;i++)
        res+=dp[w][i];
    x%=base[w];
    for(int i=w-1;i;i--){
       cur=x/base[i];
       x%=base[i];
       for(int j=0;j<cur;j++)
           if(abs(pre-j)>=2) res+=dp[i][j];
           if(i==1){
            int j=cur;
            if(abs(pre-j)>=2)res+=dp[i][cur];}
       if(abs(pre-cur)<2) break;
       pre=cur;
    }
    return res;
}
int main()
{
    pre();
    scanf("%d%d",&l,&r);
    printf("%d",cal(r)-cal(l-1));
    return 0;
}
BZOJ 1026 windy数

 

3.

BZOJ 3209 花神的数论题

对于我这种ZZ来说有点难理解

我们考虑1出现了k次的数有多少个,就可以用组合数来做。

预处理出组合数,然后从高位到低位扫,依照前两题的套路走

for(int i=(cnt==0);i<top;i++)

表示最高位是0的答案,然后往后计算最高位是1的答案
cnt表示前面已经有了多少个1,这些都是统计达到lim的答案
然后用组合数,快速幂。
技术分享
//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
typedef long long LL;
const int mod=10000007;
LL cnt,n,C[70][70],ans=1;
int w[70],top;
using namespace std;
LL ksm(LL a,LL b){
    LL base=a,res=1;
    while(b){
        if(b&1) (res*=base)%=mod;
        (base*=base)%=mod;
        b>>=1;
    }
    return res;
}
int main()
{
    scanf("%lld",&n);
    for(;n;n>>=1) w[++top]=n&1;
    for(int i=0;i<=65;i++) C[i][0]=1;
    for(int i=1;i<=65;i++)
        for(int j=1;j<=i;j++)
            C[i][j]=C[i-1][j]+C[i-1][j-1];
    for(;top;top--) if(w[top]){
        for(int i=(cnt==0);i<top;i++)
        (ans*=ksm((cnt+i),C[top-1][i]))%=mod;
        cnt++;
    }        
    printf("%lld\n",(ans*cnt)%mod);    
    return 0;
}
BZOJ 3209 花神的数论题


接下来就开始优美的记忆化搜索了,感觉记忆化搜索非常好用,套路很深,直接无脑套板,据说也不比非递归慢多少4521 手机号码

4、BZOJ 4521 手机号码

一道一看就知道怎么转移的简单题,但是用dp就。。非常难写,自己傻逼地写了个98行的WA了,网上找了一个九个for套8个if的,简直可怕。

而用记忆化搜索就非常方便了。

技术分享
//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
typedef long long LL;
LL l,r,ansl,ansr;
int lim[20],dp[20][20][20][20][20];
LL dfs(int pos,int now,int fo,int eig,int cnt,int limit){
     if(!pos) return cnt==2;
     if(!limit&&dp[pos][now][fo][eig][cnt]) return dp[pos][now][fo][eig][cnt];
     int up=limit?lim[pos]:9;
     LL res=0;
     for(int i=(pos==11);i<=up;i++){
        if((eig&&i==4)||(fo&&i==8)) continue;
        if(cnt==2) res+=dfs(pos-1,i,fo||i==4,eig||i==8,cnt,limit&&i==lim[pos]);
        else
          res+=dfs(pos-1,i,fo||i==4,eig||i==8,(i==now?cnt+1:0),limit&&i==lim[pos]); 
     }
     if(!limit) dp[pos][now][fo][eig][cnt]=res;
     return res;
}
void cal(LL x,LL &ans){
    LL tp=x;
    for(int i=1;i<=11;i++){
        lim[i]=tp%10;
        tp/=10;
    } 
    ans=dfs(11,0,0,0,0,1);
}
int main()
{
    scanf("%lld%lld",&l,&r);
    if(l!=10000000000) cal(l-1,ansl); 
    cal(r,ansr);
    printf("%lld\n",ansr-ansl);
    return 0;
}
BZOJ 4521 手机号码

 

套路 :

如果搜到底了就返回是否满足要求。

否则,若是没有到limit且已经搜过了,就返回记忆化的值。

否则就重新搜一遍。

搜完返回值,若是不是Limit就把这个结果记忆化。

 







以上是关于数位DP的主要内容,如果未能解决你的问题,请参考以下文章

数位dp小练

动态规划_计数类dp_数位统计dp_状态压缩dp_树形dp_记忆化搜索

数位DP

数位dp

HDU 2089 数位dp入门

数位DP