数位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<bits/stdc++.h>
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<bits/stdc++.h>
#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

时间: 2024-09-29 03:47:41

数位dp详解&&LG P2602 [ZJOI2010]数字计数的相关文章

数位dp详解

哈哈哈,本菜鸡经过长时间研究,终于略懂的这看似神奇的数位dp... 数位dp,顾名思义就是数位+dp,数位即是一个数的每一位上的数字,dp也就是动态规划了. 首先来讲在何时应该想到要用数位dp吧.(相信大部分人都是为了做题而学的) 数位dp的题目一般都是给定一个区间,如[l , r],然后叫你求在这区间里的数有多少个符合题目给的限制条件(解题时一般都是运用前缀和求解,即[l , r]=[0 , r]-[0 , l-1].) 其次就是讲解数位dp的原理和模板代码了.(菜鸡的我不知道讲的好不好,尽量

Luogu P2602 [ZJOI2010]数字计数

这算是一道数位DP的入门题了吧虽然对于我来说还是有点烦 经典起手式不讲了吧,\(ans(a,b)\to ans(1,b)-ans(1,a-1)\) 我们首先预处理一个东西,用\(f_i\)表示有\(i\)位数字的时候,每个数字有几个(注意是和).若不考虑前导零,则所有数字都是等价的,转移为: \(f_i=10\cdot f_{i-1}+10^{i-1}\) 这个还是比较好理解的吧,前面一项表示无论这一位放什么直接从前面拿过来已有的,所以这一位可以放\(0\to9\)十个数,后面一项表示当这一位放

P2602 [ZJOI2010]数字计数

题目描述 给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次. 输入输出格式 输入格式: 输入文件中仅包含一行两个整数a.b,含义如上所述. 输出格式: 输出文件中包含一行10个整数,分别表示0-9在[a,b]中出现了多少次. 输入输出样例 输入样例#1: 1 99 输出样例#1: 9 20 20 20 20 20 20 20 20 20 说明 30%的数据中,a<=b<=10^6: 100%的数据中,a<=b<=10^12. Solution

【题解】P2602 [ZJOI2010]数字计数

$Description: $ 给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次. \(Sample\) \(Input:\) 1 99 \(Sample\) \(Output:\) 9 20 20 20 20 20 20 20 20 20 \(Solution:\) 状态 \(f[i][j]\) 表示前 \(i\) 位当前这个数出现了 \(j\) 次 . 那么就可以愉快的dfs了,但是还有一点初始化没懂,暂时得先把他撂下了. #include<bits/

伯努利分布详解(包含该分布数字特征的详细推导步骤)

Bernouli Distribution(中文翻译称伯努利分布) 该分布研究的是一种特殊的实验,这种实验只有两个结果要么成功要么失败,且每次实验是独立的并每次实验都有固定的成功概率p. 概率公式可以表示为  , x只能为0或者1,即要么成功要么失败 根据数学期望的性质 由于这里x只有两个取值所以该分布的数学期望为 方差则可以由方差公式来计算 方差公式:  该分布显然, 因此可以得到, 所以方差  最后我们来推导该分布的最大似然估计 是这样定义的,假设我们做了N次实验,得到的结果集合为 ,我们想

[ZJOI2010] 数字计数

题目描述 给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次. 输入输出格式 输入格式: 输入文件中仅包含一行两个整数a.b,含义如上所述. 输出格式: 输出文件中包含一行10个整数,分别表示0-9在[a,b]中出现了多少次. 输入输出样例 输入样例#1: 1 99 输出样例#1: 9 20 20 20 20 20 20 20 20 20 说明 30%的数据中,\(a<=b<=10^6:\) 100%的数据中,\(a<=b<=10^{12}.\

[ZJOI2010]数字计数 题解

题面 这道题是一道数位DP的模板题: 因为窝太蒟蒻了,所以不会递推,只会记忆化搜索: 首先,咋暴力咋来: 将一个数分解成一个数组,这样以后方便调用: 数位DP的技巧:(用1~b的答案)-(1~a的答案)就是(a~b的答案): 那么对于每个数码i,我们做两次dfs(分别以a为上界和以b为上界): 设正在搜索的数码是digit: 枚举每一位,当这位==digit时,便将答案+1,并记忆化: 然后就没了; 可是这样做忽略了两个重要的事情: 1.可能存在前导零: 2.目前搜到的数比目标值要大: 对于这两

题解——[ZJOI2010]数字计数 数位DP

最近在写DP,今天把最近写的都放上来好了,,, 题意:给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次. 首先询问的是一个区间,显然是要分别求出1 ~ r ,1 ~ l的答案,然后相减得到最终答案 首先我们观察到,产生答案的区间是连续的,且可以被拆分, 也就是说0 ~ 987的贡献= 0 ~ 900 + 901 ~ 987的恭喜, 同理,把位拆开也是等价的,所以我们可以单独计算每个位的贡献 这样讲可能有点不太清晰,举个例子吧 3872 我们先把它按数拆开来

HDU 4049 Tourism Planning (状压dp 详解)

Tourism Planning Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 1125    Accepted Submission(s): 487 Problem Description Several friends are planning to take tourism during the next holiday. Th