数字计数(洛谷P2602)
数字计数(洛谷P2602)
题目大意
给定两个正整数 和 ,求在 中的所有整数中,每个数码(digit)各出现了多少次。
题解
数位dp(dfs版)
以求1的数量为例,数字9对1数量的贡献为1,数字99对1的贡献呢,十位数上对1的贡献有10,11...19,也就是10的贡献。个位数上对1的贡献有11,21..91。也就是说每个十位会有一次个位上的贡献,本来应该会有01,但是有前导零,所以没有贡献,那么999呢,百位上有100,101...199,有100的贡献,同时每个百位都会有都会有一次十位上的贡献,就会有9次,以此类推,我们会发现,如果不管前导零,个位数放满的贡献是 ,十位数放满的贡献为 ... 。所以我们有以下dp方程:
即为前 位的贡献, 为这一位的贡献。
以上都是在每个数位都满的情况进行的讨论,假如我们要对 数一数2的数量,首先对于千位上的1、2、3、4这些,个十百都是可以放满的,即1999,2999,3999,4999。而到了5这里,只有 ,这便是到了上界,是不能放满的。所以,我们记忆化搜索时,可以记忆无前导零且没有到达上界的位的贡献,如我们记录千位上的1的贡献时,单独考虑千位的贡献,记忆化后面的 ,那么千位是2、3、4时可以直接找到后3位的贡献,同时5因为到达上界需单独考虑。
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>
using ll = long long;
ll f[15], mi[15], p[15], now[15];
// 位数, 数字, 有无前导零, 是否达到上界
ll dfs(int pos, int d, bool lead, bool lim){
if(pos == 0)return 0;
//无前导零并且没达到上界
if(!lead && !lim && (~f[pos]))return f[pos]; //判断是否可以记忆化,如果f[pos] = -1,则~f[pos]=0,不满足记忆化。
ll cnt = 0;
int up = lim ? p[pos] : 9; //判断可不可以放满
for(int i = 0; i <= up; ++i){
if(lead && i == 0){ //有前导零
cnt += dfs(pos - 1, d, 1, lim && i == up);
}else if(i == d && lim && i == up){ //是需要计算答案的数字且达到了上界
cnt += now[pos - 1] + 1 + dfs(pos - 1, d, 0, 1);
}else if(i == d){ //没有到达上界,
cnt += mi[pos - 1] + dfs(pos - 1, d, 0, 0);
}else{
cnt += dfs(pos - 1, d, 0, lim && i == up);
}
}
if(!lead && !lim)f[pos] = cnt; //无前导零且没有到达上界,记忆化
return cnt;
}
ll calc(ll a, int d){
int len = 0;
memset(f, -1, sizeof(f));
while(a){
p[++len] = a % 10;
a /= 10;
now[len] = now[len - 1] + p[len] * mi[len - 1]; //主要用来处理上界
}
return dfs(len, d, 1, 1);
}
int main(void){
ll a, b;
scanf("%lld%lld", &a, &b);
mi[0] = 1;
for(int i = 1; i <= 12; ++i)mi[i] = mi[i - 1] * 10; //预处理10的幂次
for(int i = 0; i < 10; ++i){
printf("%lld ", calc(b, i) - calc(a - 1, i));
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现