[周末训练]数字计数

题目

【内存限制:$512MiB$】  【时间限制:$1000ms$】

【标准输入输出】  【题目类型:传统】  【评测方式:文本比较】


【题目描述】

原题来自:ZJOI 2010

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

【输入格式】

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

【输出格式】

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

【样例】

样例输入

1 99

样例输出

9 20 20 20 20 20 20 20 20 20

【数据范围】

$30%$的数据中,$a,b\in [1,10^6]$

$100%$的数据中,$a,b\in [1,10^{12}]$


题解

【做题经历】

做这道题的时候,如果不是老师说这是个$dp$题,我可能还会直接上暴搜虽然最后我的正解代码也是暴搜

首先,我尝试了一下手推数据,发现还是多好想的,但是始终想不出来这个题和$dp$之间有什么联系。

然后我就两眼一抹黑了

【正解】

这是一道数位$dp$题

在某谷上逛了几圈,还是发现有大佬写的$dp$正解(orz膜拜大佬),然而我太笨了,看不懂$dp$的来历(我这个蒟蒻$dp$也就这样了),含泪的时候,发现了搜索的做法。

首先,我们先把问题转化一下,用与做琪露诺数一样的方法,将题目要求的区间$[a,b]$变成区间$[1,b]$与区间$[1,a-1]$中数码出现的次数。

现在我们要解决的就是怎么求区间$[1,k]$中每个数码出现的次数了。

先看我的$dfs$部分吧

int dfs(int len,bool small,int sum,bool prezero,int d){
    if(len==0)return sum;int ret=0;
    for(int i=0;i<10;++i){
        if(small&&i>num[len])break;
        ret+=dfs(len-1,small&&(i==num[len]),sum+((!prezero||i>0)&&(i==d)),prezero&&(i==0),d);
    }
    return ret;
}

这个$dfs$是不是感觉很清晰啊。

咳咳,我来解释一下,这个$dfs$包含五个参数,他们分别是:

  • $len$:枚举到这个数的第几位了
  • $small$:是否需要小于等于右边界的对应位数
  • $sum$:显而易见,已经有多少种情况了
  • $prezero$:当前构造的数是否有前导$0$
  • $d$:当前询问的数码

其中$small$可能有点难以理解,这里我们可以举一个例子

假设我们的右边界$k=1388$

而当前我们所构造的数是$Num=\overline{13ix}$($x$是我们还没有枚举到的位数)

很显然,$i≤8$,不然$Num$就超过了右边界

而这就是$small$的用处

解决了这个问题,接下来就是搜索都会面临的问题——时间复杂度

显而易见,如果是这个代码裸交,是会被$T$飞的

所以我们需要加入记忆化减少大部分重复搜索的操作

这里而这个记忆化其实只需要把所有搜索的参数全部塞进定义和下标中就可以了(因为这个题的范围十分友善)

那么就有记忆化$dp[len][small][sum][prezero]$,其定义就是搜索对应的参数

因为我们是分字码搜索,所以最后一维可以省略了

然后就是你们最喜欢的代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
template<class T>inline void qread(T& x){
    char c;bool f=false;x=0;
    while((c=getchar())<‘0‘||‘9‘<c)if(c==‘-‘)f=true;
    for(x=(c^48);‘0‘<=(c=getchar())&&c<=‘9‘;x=(x<<1)+(x<<3)+(c^48));
    if(f)x=-x;
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
inline int rqread(){
    char c;bool f=false;int x=0;
    while((c=getchar())<‘0‘||‘9‘<c)if(c==‘-‘)f=true;
    for(x=(c^48);‘0‘<=(c=getchar())&&c<=‘9‘;x=(x<<1)+(x<<3)+(c^48));
    return f?-x:x;
}
const int MAXSIZE=20;
int l,r,num[MAXSIZE+5];
int dp[MAXSIZE+5][2][MAXSIZE+5][2];
int dfs(int len,bool small,int sum,bool prezero,int d){
    if(len==0)return sum;
    if(dp[len][small][sum][prezero]!=-1)return dp[len][small][sum][prezero];
    int ret=0;
    for(int i=0;i<10;++i){
        if(small&&i>num[len])break;
        ret+=dfs(len-1,small&&(i==num[len]),sum+((!prezero||i>0)&&(i==d)),prezero&&(i==0),d);
    }
    return dp[len][small][sum][prezero]=ret;
}
inline int solve(int var,int d){
    int len=0;
    memset(dp,-1,sizeof dp);
    while(var)num[++len]=var%10,var/=10;
    return dfs(len,1,0,1,d);
}
signed main(){
//    freopen(".in","r",stdin);
//    freopen(".out","w",stdout);
    qread(l,r);
    for(int i=0;i<10;++i)printf("%lld ",solve(r,i)-solve(l-1,i));
    putchar(‘\n‘);
    return 0;
}

原文地址:https://www.cnblogs.com/MachineryCountry/p/11612360.html

时间: 2024-08-30 12:00:37

[周末训练]数字计数的相关文章

1833: [ZJOI2010]count 数字计数

1833: [ZJOI2010]count 数字计数 Time Limit: 3 Sec  Memory Limit: 64 MBSubmit: 2951  Solved: 1307[Submit][Status][Discuss] Description 给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次. Input 输入文件中仅包含一行两个整数a.b,含义如上所述. Output 输出文件中包含一行10个整数,分别表示0-9在[a,b]中出现了多少次.

BZOJ_1833_[ZJOI2010]_数字计数_(数位dp)

描述 http://www.lydsy.com/JudgeOnline/problem.php?id=1833 统计\(a~b\)中数字\(0,1,2,...,9\)分别出现了多少次. 分析 数位dp真是细节又多又容易出错,我都懒得看题解,所以也就懒得写题解了... 注意细节吧还是... 1 #include <bits/stdc++.h> 2 using namespace std; 3 4 typedef long long ll; 5 ll a,b; 6 ll A[10],B[10],n

【BZOJ-1833】count数字计数 数位DP

1833: [ZJOI2010]count 数字计数 Time Limit: 3 Sec  Memory Limit: 64 MBSubmit: 2494  Solved: 1101[Submit][Status][Discuss] Description 给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次. Input 输入文件中仅包含一行两个整数a.b,含义如上所述. Output 输出文件中包含一行10个整数,分别表示0-9在[a,b]中出现了多少次.

BZOJ 1833 数字计数(统计[a,b]每个数字出现次数)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=1833 题意:给定区间[a,b].求区间内0到9每个数字出现的次数. 思路:f[i][j]表示到后i位是否全 0(j=1表示i位之前全0)这个状态某个数字出现的次数,p[i][j]表示这个状态后面有多少个数字.那么当前枚举到的数字为要统计的数字时,答案加 上后面还有多少种数字,即下一个状态的p值.那么我们枚举要统计的数字依次统计即可. i64 f[20][2],p[20][2]; i64

BZOJ 1833: [ZJOI2010]count 数字计数( dp )

dp(i, j, k)表示共i位, 最高位是j, 数字k出现次数. 预处理出来. 差分答案, 对于0~x的答案, 从低位到高位进行讨论 ------------------------------------------------------------------------------ #include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 16; const int N =

【BZOJ 1833】 [ZJOI2010]count 数字计数

1833: [ZJOI2010]count 数字计数 Time Limit: 3 Sec Memory Limit: 64 MB Submit: 1697 Solved: 753 [Submit][Status][Discuss] Description 给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次. Input 输入文件中仅包含一行两个整数a.b,含义如上所述. Output 输出文件中包含一行10个整数,分别表示0-9在[a,b]中出现了多少次.

[ZJOI2010][BZOJ1833] count 数字计数|模拟

1833: [ZJOI2010]count 数字计数 Time Limit: 3 Sec  Memory Limit: 64 MBSubmit: 1976  Solved: 873[Submit][Status][Discuss] Description 给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次. Input 输入文件中仅包含一行两个整数a.b,含义如上所述. Output 输出文件中包含一行10个整数,分别表示0-9在[a,b]中出现了多少次.

bzoj1833: [ZJOI2010]count 数字计数(数位DP+记忆化搜索)

1833: [ZJOI2010]count 数字计数 题目:传送门 题解: 今天是躲不开各种恶心DP了??? %爆靖大佬啊!!! 据说是数位DP裸题...emmm学吧学吧 感觉记忆化搜索特别强: 定义f[i][j][k]表示若前i个位置有k个j的此时的全局方案数,然后就可以记忆化搜索了(具体看代码吧) 代码: 1 #include<cstdio> 2 #include<cstring> 3 #include<cstdlib> 4 #include<cmath>

16-算法训练 数字三角形

http://lx.lanqiao.cn/problem.page?gpid=T312 算法训练 数字三角形 时间限制:1.0s   内存限制:256.0MB 问题描述 (图3.1-1)示出了一个数字三角形. 请编一个程序计算从顶至底的某处的一条路 径,使该路径所经过的数字的总和最大. ●每一步可沿左斜线向下或右斜线向下走: ●1<三角形行数≤100: ●三角形中的数字为整数0,1,…99: . (图3.1-1) 输入格式 文件中首先读到的是三角形的行数. 接下来描述整个三角形 输出格式 最大总