数字计数(数位dp)

题目描述

链接

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

输入格式

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

输出格式

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

输入

1 99

输出

9 20 20 20 20 20 20 20 20 20

数据规模与约定

对于 30 % 30\% 30% 的数据,保证 a ≤ b ≤ 1 0 6 a\le b\le10^6 ab106

对于 100 % 100\% 100% 的数据,保证 1 ≤ a ≤ b ≤ 1 0 12 1\le a\le b\le 10^{12} 1ab1012

思路

首先我们要清楚 数 位 d p 数位dp dp 解决的是什么问题:
求出在给定区间 [ A , B ] [A,B] [A,B] 内,符合条件 f ( i ) f(i) f(i) 的数 i i i 的个数。条件 f ( i ) f(i) f(i) 一般与数的大小无关,而与数的组成有关
由于数是按位dp,数的大小对复杂度的影响很小

上面的话摘自:【洛谷日报#84】数字组成的奥妙——数位dp

关于数位dp,这个链接也讲的很好:算法学习笔记(68): 数位DP

先放上代码,再讲讲对一些细节的理解。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int l,r,len,dp[14][14][2][2],a[14];

int dfs(int pos,int digit,int num,int lim,int lead){
	if(pos>len) return num;
	if(dp[pos][num][lim][lead]!=-1) return dp[pos][num][lim][lead];
	int res=lim?a[len-pos+1]:9,ret=0;
	for(int i=0;i<=res;i++){
		if(lead&&i==0) ret+=dfs(pos+1,digit,num,lim&&i==res,1);
		else if(lead&&i!=0) ret+=dfs(pos+1,digit,num+(i==digit),lim&&i==res,0);
		else ret+=dfs(pos+1,digit,num+(i==digit),lim&&i==res,0);
	}
	return dp[pos][num][lim][lead]=ret;
}

int part(int x,int v){
	len=0;
	while(x) a[++len]=x%10,x/=10;
	memset(dp,-1,sizeof(dp));
	return dfs(1,v,0,1,1);
}

signed main(){
	cin>>l>>r;
	for(int i=0;i<=9;i++){
		cout<<part(r,i)-part(l-1,i)<<" \n"[i==9];
	}
}

int dfs(int pos,int digit,int num,int lim,int lead)
这个深搜会计算出,在已经确定了前 p o s − 1 pos-1 pos1 位数字的情况下(也就是高 p o s − 1 pos-1 pos1 位),并且在前 p o s − 1 pos-1 pos1 位中,目标数字 d i g i t digit digit 已经出现过 n u m num num 次的情况下,数字 d i g i t digit digit 最终会出现的次数。

lim 代表当前位数字的取值有没有限制,lead 代表前 p o s − 1 pos-1 pos1 位是不是全为前置 0 0 0 。这两个参数比较固定。

举个例子,dfs(2,5,1,0,0) ,假设这时 l e n = = 2 len==2 len==2。那么这个 d f s dfs dfs 就是要计算出,一共有两位数,最高位已经确定了,并且 5 5 5 这个数字在最高位出现过,最低为的取值没有限制,当前位也不可能是前导零的情况下, 5 5 5 这个数字,在这两位数中一共出现的次数。

也即是说,要计算 5_ (下划线代表数字未确定),所有取值中, 5 5 5 最终会出现的次数,那么,大家可以带入代码中算一下,最终返回值是 11 11 11.

那返回 11 11 11 到底正不正确呢?枚举一下,5_ 可以转换成十个数:

50、51、52、53、54、5556、57、58、59

5 5 5 最终会出现 11 11 11 次。

最终要求每个数码出现的次数,相当于做 10 10 10 次数位dp,每次计算一个数码。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

m0_51864047

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值