数位dp 学习指南

什么是数位 DP


在信息学竞赛中,有一类难度不大但异常麻烦的问题——数位计数问题,这类问题的主
要特点是询问的答案和一段连续的数的各个数位相关,并且需要对时间效率有一定要求。由
于解决这类问题往往意味着巨大的代码量,而众多的特殊情况又意味着出现错误的巨大可能
性,因此很少有人愿意解决此类问题,但只要掌握好的方法,解决这类问题也并非想象中的
那样困难。 

数位DP的解题思路

对于一个数,若其首位已经比询问上界小,则剩余位没有任何限制。此时如果能直接处理这一情况,则问题距离解决又会迈出一大步。 
例如,在十进制下,计算[10000,54321]内的数字和,我们可以将其分解为: 
[10000,19999],[20000,29999],[30000,39999],[40000,49999],[50000,54321]。 
前四个区间如果可以直接解决,则只需处理最后一个区间,进一步将最后一个区间划分
为:[50000,50999],[51000,51999],[52000,52999],[53000,53999],[54000,54321]。同理将最后一
个区间划分下去,最后可以得到以下区间划分: 
[10000,19999],[20000,29999],[30000,39999],[40000,49999], 
[50000,50999],[51000,51999],[52000,52999],[53000,53999], 
[54000,54099],[54100,54199],[54200,54299], 
[54300,54309],[54310,54319], 
[54320,54321] 

数位DP的经典模板

该模板出处不详,但是此模板大大降低了数位dp类问题的难度。

typedef long long LL;
const int maxn=22;
int dig[maxn];
LL f[maxn]/* [TODO] */;

LL dfs(int pos,/* TODO */,int limit){
    if (pos<0) return /* TODO */;
    if (!limit&&f[pos]/* [TODO] */!=-1) return f[pos]/* [TODO] */;
    LL res=0;
    int last=limit?dig[pos]:9;
    for (int i=0;i<=last;i++){
        res+=dfs(pos-1,/* TODO */,limit&&(i==last));
    }
    if (!limit) f[pos]/* [TODO] */=res;
    return res;
}

LL solve(LL n){
    int len=0;
    while (n){
        dig[len++]=n%10;
        n/=10;
    }
    return dfs(len-1,/* TODO */,1);
}


经典题目


Hdu 2089 不要62

求给定区间中不含有62和4的数的个数。

LL dfs(int pos,int pre,int fg,int limit){
    if (pos<0) return fg==0;
    if (!limit&&f[pos][pre][fg]!=-1) return f[pos][pre][fg];
    LL res=0;
    int last=limit?dig[pos]:9;
    for (int i=0;i<=last;i++){
        res+=dfs(pos-1,i,fg||((pre==6)&&(i==2))||(i==4),limit&&(i==last));
    }
    if (!limit) f[pos][pre][fg]=res;
    return res;
}



Hdu 3555 Bomb

求给定区间的含有49的数的个数。

LL dfs(int pos,int pre,int istrue,int limit){
    if (pos<0) return istrue;
    if (!limit && f[pos][pre][istrue]!=-1) return f[pos][pre][istrue];
    int last=limit?dig[pos]:9;
    LL ret=0;
    for (int i=0;i<=last;i++){
        int ok=(pre==4)&&(i==9);
        ret+=dfs(pos-1,i,istrue||ok,limit&&(i==last));
    }
    if (!limit) f[pos][pre][istrue]=ret;
    return ret;
}


windy数

求给定区间范围内的,求相邻数位之差绝对值不小于2的数的个数。

LL dfs(int pos,int pre,int fg,int limit){
    if (pos<0) return 1;
    if (!limit && f[pos][pre][fg]!=-1) return f[pos][pre][fg];
    int last=limit?dig[pos]:9;
    LL ret=0;
    for (int i=0;i<=last;i++){
        if (fg==0||abs(i-pre)>=2)
            ret+=dfs(pos-1,i,fg||i,limit&&(i==last));
    }
    if (!limit) f[pos][pre][fg]=ret;
    return ret;
}


Hdu 3709 Balanced Number

平衡数。数n以数n中的某个位为支点,每个位上的数权值为(数字xi*(posi - 支点的posi)),如果数n里有一个支点使得所有数权值之和为0那么她就是平衡数。比如4139,以3为支点,左边 = 4 * (4 - 2) + 1 * (3 - 2) = 9,右边 = 9 * (1 - 2) = -9,左边加右边为0,所以4139是平衡数。现在给出一个区间[l,r],问区间内平衡数有多少个?

LL dfs(int pos,int o,int pre,int limit){
    LL res=0;
    if (pos<0)    return pre==0;
    if (!limit&&f[pos][o][pre]!=-1) return f[pos][o][pre];
    int last=limit?dig[pos]:9;
    for (int i=0;i<=last;i++){
        res+=dfs(pos-1,o,pre+i*(pos-o),limit&&(i==last));
    }
    if (!limit) f[pos][o][pre]=res;
    return res;
}

LL solve(LL n){
    if (n<0) return 0;
    if (n==0) return 1;
    int len=0;
    while (n){
        dig[len++]=n%10;
        n/=10;
    }
    LL ans=0;
    for (int i=0;i<len;i++){
        ans+=dfs(len-1,i,0,1);
    }
    ans=ans-len+1;
    return ans;
}


CodeForces 55D Beautiful numbers

如果一个数能够被其每个数位的数都整除,那么这个数就叫做美丽数。

int check(int bit,int mod){
    for (int i=2;i<=9;i++){
        if (bit&(1<<(i-2))){
            if (mod%i) return 0;
        }
    }
    return 1;
}

LL dfs(int pos,int bit,int mod,int limit){
    if (pos<0) return check(bit,mod);
    if (!limit && f[pos][bit][mod]!=-1) return f[pos][bit][mod];
    int last=limit?dig[pos]:9;
    LL ret=0;
    for (int i=0;i<=last;i++){
        int nbit=bit;
        if (i>=2) nbit|=1<<(i-2);
        ret+=dfs(pos-1,nbit,(mod*10+i)%MOD,limit&&(i==last));
    }
    if (!limit) f[pos][bit][mod]=ret;
    return ret;
}



Hdu 3652 B-number

求小于n是13的倍数且含有'13'的数的个数。

LL dfs(int pos,int pre,int fg,int md,int limit){
    if (pos<0) return fg&&(md==0);
    if (!limit&&f[pos][pre][fg][md]!=-1) return f[pos][pre][fg][md];
    LL res=0;
    int last=limit?dig[pos]:9;
    for (int i=0;i<=last;i++){
        res+=dfs(pos-1,i,fg||(pre==1&&i==3),(md*10+i)%13,limit&&(i==last));
    }
    if (!limit) f[pos][pre][fg][md]=res;
    return res;
}


Hdu 4352 XHXJ's LIS

求[L,R]内最长递增子序列是k的数的个数;

LL f[maxn][1<<10][11];
int K;
int dig[maxn];
int getLIS(int bit){
    int res=0;
    for (int i=0;i<10;i++){
        if (bit&(1<<i)) res++;
    }
    return res;
}
int gaoBit(int bit,int d){
    if (bit&(1<<d)) return bit;
    if ((1<<d)>bit) return bit|(1<<d);
    bit|=(1<<d);
    for (int i=d+1;i<10;i++){
        if (bit&(1<<i)) return bit^(1<<i);
    }
    return 0;
}

LL dfs(int pos,int bit,int limit){
    if (pos<0) return getLIS(bit)==K;
    if (!limit&&f[pos][bit][K]!=-1) return f[pos][bit][K];
    LL res=0;
    int last=limit?dig[pos]:9;
    for (int i=0;i<=last;i++){

        int go;
        if (bit==0&&i==0) go=0;
        else go=gaoBit(bit,i);
        res+=dfs(pos-1,go,limit&&(last==i));
    }
    if (!limit) f[pos][bit][K]=res;
    return res;
}

LL solve(LL n){
    int len=0;
    while (n){
        dig[len++]=n%10;
        n/=10;
    }
    return dfs(len-1,0,1);
}


HDU 4507 吉哥系列故事——恨7不成妻

中文题。需要推公式。


#include <iostream>
#include <cstring>

using namespace std;
typedef long long LL;
const int maxn=22;
const int MOD=1e9+7;


typedef pair<LL,LL> PII;
typedef pair<PII,LL> PIII;

inline LL fst(PIII a){
    return a.first.first;
}
inline LL sec(PIII a){
    return a.first.second;
}
inline LL thd(PIII a){
    return a.second;
}
inline PIII makeZeroPIII(){
    return make_pair(make_pair(0,0),0);
}
inline PIII makeOnePIII(){
    return make_pair(make_pair(1,0),0);
}
LL power[maxn];
PIII f[maxn][11][8];
bool v[maxn][11][8];
int dig[maxn];

PIII gao(PIII a, PIII b,int i,int pos){
    PIII res=makeZeroPIII();
    res.first.first=(fst(a)+fst(b))%MOD;//数量
    res.first.second=(sec(a)+sec(b)+((i*power[pos])%MOD*fst(b))%MOD)%MOD;//和
    res.second=(thd(a)+thd(b)+((2*i*power[pos])%MOD*sec(b))%MOD+(((i*i*power[pos])%MOD*power[pos])%MOD*fst(b))%MOD)%MOD;//平方和
    return res;
}

PIII dfs(int pos,int pre,int sev,int limit){
    if (pos<0) {
        if (pre!=0&&sev!=0) return makeOnePIII();
        else return makeZeroPIII();
    }
    if (!limit&&v[pos][pre][sev]) return f[pos][pre][sev];
    PIII res=makeZeroPIII();
    int last=limit?dig[pos]:9;
    for (int i=0;i<=last;i++){
        if (i==7) continue;
        PIII tmp=dfs(pos-1,(pre*10+i)%7,(sev+i)%7,limit&&(i==last));
        res=gao(res,tmp,i,pos);
    }
    if (!limit){
        v[pos][pre][sev]=true;
        f[pos][pre][sev]=res;
    }
    return res;
}

LL solve(LL n){
    int len=0;
    while (n){
        dig[len++]=n%10;
        n/=10;
    }
    PIII ans = dfs(len-1,0,0,1);
    return thd(ans);
}

int main()
{
    memset(v,0,sizeof(v));
    memset(power,0,sizeof(power));
    power[0]=1;
    for (int i=1;i<maxn;i++){
        power[i]=(power[i-1]*10)%MOD;
    }
    int T;
    cin>>T;
    while (T--){
        LL a,b;
        cin>>a>>b;
        cout<<(solve(b)-solve(a-1)+MOD)%MOD<<endl;
    }
    return 0;
}





  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值