解题思路
首先区间[a,b][a,b][a,b]内的数量可转化为[1,b]−[1,a−1][1,b]-[1,a-1][1,b]−[1,a−1]。考虑求一个数码出现的次数,比如1出现的次数。我们首先想爆搜怎么写,然后加一个记忆化即可。
假如对于一个长度为lenlenlen的数,从高位到低位枚举它每一位上的数字,然后计算111出现的次数。哪些东西要记到状态里去?
第一,当前的位置lenlenlen一定要记的。
第二,你要记录当前数位有没有比c[len]小,是一个boolboolbool值,因为这个用来确定你枚举下一位的范围。
第三,要记录当前1已经出现的次数。
第四,由于前导0不能算,还需记录之前是否是前导000,也是一个boolboolbool值。
代码
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
long long power[20],l,r,f[20][15],c[20];
long long dp(int len,int limit,int zero,int sum,int x){
if(!len)return sum;
if(!limit&&!zero&&f[len][sum]!=-1)return f[len][sum];
long long ssum=0,maxx=limit?c[len]:9;//由于我们是从高位到低位枚举的,所以如果之前一位的数码和最大数的数码相同,这一位就只能枚举到c[len];
否则如果之前一位比最大数的数码小,那这一位就可以从0~9枚举了。
for(int i=0;i<=maxx;i++)
{
ssum+=dp(len-1,(i==maxx)&&limit,i==0&&zero,sum+((i||!zero)&&(x==i)),x);
//继续搜索,数位减一,limit的更新要看之前有没有相等,且这一位有没有相等;
//zero的更新就看之前是否为前导0且这一位继续为0;
//sum的更新要看之前是否为前导0或者这一位不是0;
//x继续传进去。
}
if(!limit&&!zero)f[len][sum]=ssum;
return ssum;
}
long long solve(long long a,int x)
{
int pos=0;
long long k=a;
while(k>0)
{
c[++pos]=k%10;
k/=10;
}
return dp(pos,1,1,0,x);
}
int main(){
power[0]=1;
for(int i=1;i<=10;i++)
power[i]=power[i-1]*10;
scanf("%lld%lld",&l,&r);
for(int i=0;i<=9;i++)
{
memset(f,-1,sizeof(f));
printf("%lld ",solve(r,i)-solve(l-1,i));
}
}