题目描述
给定两个正整数 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 0∼9 在 [ 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 a≤b≤106 ;
对于 100 % 100\% 100% 的数据,保证 1 ≤ a ≤ b ≤ 1 0 12 1\le a\le b\le 10^{12} 1≤a≤b≤1012 。
思路
首先我们要清楚 数 位 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
pos−1 位数字的情况下(也就是高
p
o
s
−
1
pos-1
pos−1 位),并且在前
p
o
s
−
1
pos-1
pos−1 位中,目标数字
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
pos−1 位是不是全为前置
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、55、56、57、58、59
5 5 5 最终会出现 11 11 11 次。
最终要求每个数码出现的次数,相当于做 10 10 10 次数位dp,每次计算一个数码。