数位dp入门

数位DP专题练习

A - Bomb (HDU 3555)传送门

求1~n中不包含49的数的个数
考虑dp

#include
#include
#include
#include
#include
using namespace std;
typedef unsigned long long ULL;
ULL n,t,ans,m;
ULL dp[20][3],buf[20];
void init() {
    dp[0][0]=1; dp[0][1]=0; dp[0][2]=0;
    for (ULL i=1;i<=22;i++) {
        dp[i][0]=dp[i-1][0]*10-dp[i-1][1]; //长度为i不含49的方案数
        dp[i][1]=dp[i-1][0];               //长度为i不含49以9开头的方案数
        dp[i][2]=dp[i-1][2]*10+dp[i-1][1]; //长度为i且包含49的方案数
    }  
}
void work() {
    memset(buf,0,sizeof(buf)); m=0;
    while (n) {
        buf[++m]=n%10;
        n/=10;
    }
    buf[m+1]=0;
}
int main() {
    init(); cin>>t;
    while (t--) {
        cin>>n; n++; work();
        ULL ans=0;
        bool flag=false;
        for (ULL i=m;i>=1;i--) {
            ULL x=buf[i];
            ans+=dp[i-1][2]*x;
            if (flag) {
                ans+=x*dp[i-1][0];    		 //相当于i-1位任意取
                continue;
            }
            if (x>4) ans+=dp[i-1][1];
            if (buf[i]==9 && buf[i+1]==4) flag=true;
        }
        cout<<ans<<endl;
    }
    return 0;
}

B - 不要62 (HDU 2089)传送门

求给定的a~b区间内不含62和4的数的个数
刚开始用的第一题一样的dp

#include
#include
#include
#include
#include
using namespace std;
const int N=15;
int n,m;
int dp[N][5],c[N];
void init() {
    memset(dp,0,sizeof(dp));
    dp[0][0]=1;
    for(int i=1;i<=8;i++) {
        dp[i][0]=dp[i-1][0]*9-dp[i-1][1];             //长度为i且不包含62和4的个数
        dp[i][1]=dp[i-1][0];                          //长度为i以2开头且不包含62或4的个数
        dp[i][2]=dp[i-1][2]*10+dp[i-1][1]+dp[i-1][0]; //长度为i且包含62或4的个数
    }
}
int work(int u) {
    int res=0;
    memset(c,0,sizeof(c));
    while (u!=0) {
        c[++res]=u%10;
        u/=10;
    }
    c[res+1]=0;
    return res;
}
int solve(int u) {
    int res=0;
    int len=work(u);
    bool flag=false;
    for(int i=len;i>=1;i--) {
        res+=c[i]*dp[i-1][2];
        if(flag) {
            res+=c[i]*dp[i-1][0];
            continue;
        }
        if(c[i]>4) res+=dp[i-1][0];
        if(c[i]>6) res+=dp[i-1][1];
        if(c[i+1]==6 && c[i]>2) res+=dp[i][1];
        if(c[i]==4 || (c[i+1]==6 && c[i]==2)) flag=true;
    }
    return u-res;
}
int main() {
    init();
    while (~scanf("%d%d",&n,&m) && (n || m)) {
        printf("%d\n",solve(m+1)-solve(n));
    }
    return 0;
}

记忆化搜索写法

#include
#include
#include
#include
using namespace std;
const int N=15;
int n,m;
int c[N],dp[N][2];          //pos表示当前位数 pre表示是否有前导的6 limit表示当前位数是否有上限
int dfs(int pos,bool pre,bool limit) {
    if (pos==0) return 1;
    if (!limit && dp[pos][pre]) return dp[pos][pre];       //记忆化
    int res=0,maxx=limit ? c[pos]:9;
    for (int i=0;i<=maxx;i++) {
        if (i==4 || pre && i==2) continue;
        res+=dfs(pos-1,i==6,limit && i==maxx);
    }
    return limit ? res:dp[pos][pre]=res;                     //如果有上限就不能记录答案
}
int solve(int u) {
    int k=0;
    while (u) {
        c[++k]=u%10;
        u/=10;
    }
    c[k+1]=0;
    return dfs(k,false,true);
}
int main() {
    while (~scanf("%d%d",&n,&m)) {
        if (!n && !m) continue;
        printf("%d\n",solve(m)-solve(n-1));
    }    
    return 0;
}

C - B-number (HDU 3652) 传送门

找出1~n范围内含有13并且能被13整除的数字的个数

#include
#include
#include
#include
using namespace std;
const int N=15;
int n,c[N],dp[N][N][3];
//state==0 什么都没有 state==1 前一位1 state==2 含有13
//mod表示到当前位的模数
int dfs(int pos,int mod,int state,bool limit) {
    if (pos==0) return mod==0 && state==2;
    if (!limit && dp[pos][mod][state]) return dp[pos][mod][state];
    int res=0,maxx=limit ? c[pos]:9;
    for (int i=0;i<=maxx;i++) {
        int status=0;
        if (state==2 || state==1 && i==3) status=2;
        else if (i==1) status=1;
        res+=dfs(pos-1,(mod*10+i)%13,status,limit && i==maxx);
    }
    return limit ? res:dp[pos][mod][state]=res;
}
int solve(int u) {
    int k=0;
    while (u) {
        c[++k]=u%10;
        u/=10;
    }
    c[k+1]=0;
    return dfs(k,0,0,true);
}
int main() {
    while (~scanf("%d",&n)) {
        printf("%d\n",solve(n));
    }
}

总结一下
数位dp相当于用dfs爆搜每一位的状态
dfs里用到什么就开几维数组去维护它,实现记忆化搜索

你可能感兴趣的:(Algorithm,Hdu)