传送门1
传送门2
写在前面:有关数位DP的题目都会做很久吗……
思路:
(神犇学弟遇到题目的加强版,暴力变DP)
30分直接暴力枚举拆位
100分考虑类似前缀和的做法,我们只要得到[0,a]的各位数码出现次数,同理可得[0,b]的出现次数,作差即可,这里就需要用到数位DP
转移并不困难,f[i][j][k]表示最多是i位数,且最高位为j的所有数中k的出现次数,具体的范围表达可以看图
显然i=1~12,j,k=0~9。而且,f[i][j][k]是可以转移f[i][j+1][k](j<9)和f[i+1][0][k](j=9),注意这里的f[i+1][0][k]表示最高位为0,但实际上就是f[i][9][k]
转移完后我们就可以直接得到0到1..9,19,29..99,199,299..999,1999…….各位数码出现的次数了,但这显然不能满足我们的要求,所以我们可以分块计算,比如12345,我们可以得到0~9999的值,再得到0~1999(实际上是10000~11999,但首位的1恒定,所以我们可以直接计算得到),以此类推到0~299,0~39,0~5。
(感觉还是各种口胡说不清楚,如果有问题还是评论吧)
注意:
1.个位注意单独计算
2.我们并没有办法通过f[i][j][0]直接得到0出现的次数,但它就是总出现次数-1..9出现的次数,所以我们只要算出总出现次数就可以了
3.sum开的int,没有1A不开心
#include<bits/stdc++.h>
#define LL long long
using namespace std;
LL a,b;
LL f[14][10][10],p[13];
struct node
{
LL sum[10];
void prin()
{
for (int i=0;i<=9;i++)
{
printf("%lld",sum[i]);
if (i<9) putchar(' ');
}
}
void clear(){memset(sum,0,sizeof(sum));}
};
node sub(node a,node b)
{
node c;
for(int i=0;i<=9;i++) c.sum[i]=a.sum[i]-b.sum[i];
return c;
}
node solve(LL x)
{
node y;
y.clear();
if (x<=0)
{
y.sum[0]=(x==0);
return y;
}
for (int i=1;i<=9;i++)
{
LL t=x,k=0,wei=log10(x);
while (t)
{
if (t/p[wei])
y.sum[i]+=f[wei+1][t/p[wei]-1][i]+k*(t/p[wei]*p[wei]);
k+=(i==t/p[wei]);t%=p[wei];wei--;
}
y.sum[i]+=k;
}
LL tot=0,wei=log10(x);
while (wei>=0)
{
tot+=(wei+1)*(x-p[wei]+1+(x<10));
x=p[wei]-1;
wei--;
}
for (int i=1;i<=9;i++) tot-=y.sum[i];
y.sum[0]=tot;
return y;
}
main()
{
scanf("%lld%lld",&a,&b);
p[0]=1;
for(int i=1;i<=12;i++) p[i]=p[i-1]*10;
f[1][0][0]=1;
for (int i=1;i<=12;i++)
for(int j=0;j<=9;j++)
for (int k=0;k<=9;k++)
if (j<9)
f[i][j+1][k]+=f[i][j][k]+f[i-1][9][k]+p[i-1]*(j+1==k);
else
f[i+1][0][k]+=f[i][j][k];
sub(solve(b),solve(a-1)).prin();
}