Acwing 338 计数问题(数位dp+前导0处理)

题意

给定 [ a , b ] [a,b] [a,b],求区间中所有数字中0-9出现的次数。

例如,a=1024,b=1032,则 a 和 b 之间共有9个数如下:

1024 1025 1026 1027 1028 1029 1030 1031 1032

其中‘0’出现10次,‘1’出现10次,‘2’出现7次,‘3’出现3次等等…

思路

理解了数位DP的话,这就是一道简单题了,这里主要是为处理前导0做个笔记,方便以后查阅。

在DFS中是用lead标记是否有前导0限制,lead==1表示有前导0限制。

前导0限制(表示最高位不能取0,例如0123,应该表示成123)向下一层传递的条件是lead==1&&i==0也就是说当前位有前导0限制,并且当前位取0的时,下一层有前导0限制。

需要记录的是,前 i i i位满足条件的数的个数 f [ i ] . c n t f[i].cnt f[i].cnt,和前 i i i位每个数字出现的次数。

递推公式:

i i i位取 j j j

f[i].cnt+=f[i-1].cnt;
f[i].a[j]+=f[i-1].cnt;//第i位取j时,前i-1为满足条件的数个数就是第i位j出现的次数
for(int j=0;j<=9;j++)f[i].a[j]+=f[i-1].a[j];

代码

#include 
using namespace std;
struct node{
    int cnt;
    int a[10];
    node(){
        cnt=0;
        memset(a,0,sizeof a);
    }
}f[12];
int a[12];
inline node dfs(int pos,bool limit,bool lead){
    if(pos==-1){
        node tmp;
        tmp.cnt=1;
        return tmp;
    }

    if(!limit&&!lead&&f[pos].cnt!=-1)return f[pos];
    int up=limit?a[pos]:9;
    node res;
    for(int i=0;i<=up;i++){
        //if(lead&&i==0)continue;
        node tmp=dfs(pos-1,limit&&i==a[pos],lead&&i==0);
        res.cnt+=tmp.cnt;

        if(!(lead&&i==0)) res.a[i]+=tmp.cnt;

        for(int i=0;i<=9;i++)res.a[i]+=tmp.a[i];
    }
    return (limit||lead)?res:f[pos]=res;
}
inline node sol(int n){
    if(!n){
        node tmp;
        return tmp;
    }

    memset(f,-1,sizeof f);
    int pos=0;
    while (n)a[pos++]=n%10,n/=10;

    return dfs(pos-1, true,true);
}
int main(){
    int l,r;
    while (cin>>l>>r,l||r){
        if(r<l)swap(l,r);

        node a=sol(r);
        node b=sol(l-1);

        for(int i=0;i<=9;i++){
            if(i)cout<<" ";
            cout<<a.a[i]-b.a[i];
        }
        cout<<endl;
    }
    return 0;
}

你可能感兴趣的:(动态规划-数位DP)