数位dp详解&&LG P2602 [ZJOI2010]数字计数

数位dp,适用于解决一类求x~y之间有多少个符合要求的数或者其他。

例题

题目描述

杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。

不吉利的数字为所有含有4或62的号码。例如:

62315 73418 88914

都属于不吉利号码。但是,61152虽然含有6和2,但不是62连号,所以不属于不吉利数字之列。

你的任务是,对于每次给出的一个牌照区间号,推断出交管局今次又要实际上给多少辆新的士车上牌照了。

输入输出格式

输入的都是整数对A、B(0<A≤B<10^9),如果遇到都是0的整数对,则输入结束。

数据规模

20% 的数据,满足 1≤A≤B≤10^6;

100%的数据,满足 1≤A≤B≤2×10^9。

解法

solve(x)求0~x中符合要求的数有几个;那么答案显然就是solve(b)-solve(n-1)

那么如何solve呢?用记忆化搜索(不用搜索而用普通dp也行,但要麻烦一点)

我们考虑从高位往低位枚举:

  • 如果前面的所有位都“取到顶了”,那么下一位只能取0~这一位:比如4375前两位取了43,那么下一位只能取0~这一位,也就是0~7

  • 否则,下一位可以取0~9

我们使用flag来表示前几位有没有“取到顶”

f[l][lst]表示的是整个长度为l的数的前面一位是lst的数有多少个。比如f[3][5]表示的就是形如5 ___ ___ ___的数有多少个(注意:不是形如5 ___ ___!)

需要注意的是,只能在flag=0的情况下才能记忆化,因为flag=0时后面是可以取满的;flag=1时后面取不满,而且上限是不定的。

具体实现详见注释。

#include
using namespace std;
    if(!flag&&f[l][lst]!=-1) return f[l][lst];//记忆化
    int u=flag?d[l]:9,anstmp=0;
    for(int i=0;i<=u;i++)
        if(i!=4&&!(lst==6&&i==2)) //这一位4或者前一位是6这一位是2(也就是组成62)是不行的。
            anstmp+=dfs(l-1,i,flag&&i==u);
    return flag?anstmp:f[l][lst]=anstmp;//只有flag=0时才能记忆化!
}
inline int solve(int k)
{
    cnt=0;
    while(k)
    {
        d[++cnt]=k%10;
        k/=10;
    }//先将当前的数一位一位拆开
    return dfs(cnt,0,1);
}
int main()
{
    memset(f,-1,sizeof(f));
    while(scanf("%d %d",&n,&m)!=EOF)
    {
        if(n==m&&n==0) break;
        printf("%d\n",solve(m)-solve(n-1));
    }
    return 0;
}

P2602 [ZJOI2010]数字计数

题目描述

给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码 (digit) 各出现了多少次。

输入格式

仅包含一行两个整数 a,b,含义如上所述。

输出格式

包含一行 10个整数,分别表示 0∼9在[a,b]中出现了多少次。

数据规模

30%的数据中,1≤a≤b≤10^6;

100%的数据中,1≤a≤b≤10^12。

实现

ans[0~9]表示题目的答案。

f[l][lst][11]:f[l][lst][0~9]表示0~9各有多少个,f[l][lst][10]表示有几个数

f[l][lst][10]很好算,f[l][lst][0~9]直接算不好算,我们采取在dfs前和dfs后的ans数组做差的方法求出。

dfs中的tstep是调试用的,不用管;dfs中的t是表示加或者减的,因为是0~m的答案减去0~n-1的答案,所以求0~m时t=1,0~n-1时t=-1

#include
#define ll long long
using namespace std;
ll n,m,cnt=0,d[1005],f[1005][1005][11],k,b,ans[1005],bf[1005];
ll dfs(ll l,ll lst,bool flag,bool flagg,ll t,ll tstep)
{
    if(l==0) return 1;
    if(!flag&&!flagg&&f[l][lst][10]!=-1) 
    {
        for(ll i=0;i<=9;i++) ans[i]+=f[l][lst][i]*t;
        return f[l][lst][10];
    }
    ll u=(flag?d[l]:9),anstmp=0;
    for(int i=0;i<=9;i++) bf[i]=ans[i];
    for(ll i=0;i<=u;i++)
    {
        ll tttmp=dfs(l-1,i,flag&&i==u,flagg&&i==0,t,tstep+1);
        if(i!=0||!flagg)
        {
            ans[i]+=tttmp*t;
            anstmp+=tttmp;
        }
    }
    if(!flag&&!flagg) {for(int i=0;i<=9;i++) {f[l][lst][i]=abs(ans[i]-bf[i]);}}
    return (flag||flagg)?anstmp:f[l][lst][10]=anstmp;
}
inline ll solve(ll k,ll t)
{
    cnt=0;
    while(k)
    {
        d[++cnt]=k%10;
        k/=10;
    }
    return dfs(cnt,0,1,1,t,0);
}
int main()
{
    memset(f,-1,sizeof(f));
    scanf("%lld %lld",&n,&m);
    solve(m,1);
    solve(n-1,-1);
    for(ll i=0;i<=8;i++) printf("%lld ",ans[i]);
    printf("%lld",ans[9]);
    return 0;
}

转载于:https://www.cnblogs.com/LJB00125/p/shuweidp-and-LG-P2602-tijie.html

你可能感兴趣的:(数位dp详解&&LG P2602 [ZJOI2010]数字计数)