bzoj1833
Description
给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次。
Input
输入文件中仅包含一行两个整数a、b,含义如上所述。
Output
输出文件中包含一行10个整数,分别表示0-9在[a,b]中出现了多少次。
Sample Input
1 99
Sample Output
9 20 20 20 20 20 20 20 20 20
HINT
30%的数据中,a<=b<=10^6;
100%的数据中,a<=b<=10^12。
最近做数位DP的题总是出各种错误,调了好久才出来。数位DP就是这样,一点细节都不能错。
下面进入正题。
f[i][j]代表在无范围限制下填到第i位数码j出现的次数。
g[i][j]代表在不超过上界x的前提下填到第i位数码j出现的次数。
填到第i位时 cnt1:前导0的个数 cnt2: 10i−1 cnt3:s[len-i..len-1]所组成的数。
那么转移时
cnt3=(s[len-i]-'0')* cnt2+cnt3; cnt2*=10; cnt1=cnt2+cnt1;
转移f时枚举第i为填j时
f[i][l]+=f[i-1][l] ,f[i][j]+=cnt2;(0<=j<=9,0<=l<=9)
转移g时先枚举第i为填j(0<=j<原数中该位)此时1-i-1位都没有限制,用f转移,第i为为原数中该位时就该用g来转移;也就是
g[i][l]+=f[i-1][l],g[i][j]+=cnt2;(j< s[len-i-1]-'0',0<=l<=9)
g[i][l]+=g[i-1][l], g[i][j]+=cnt3+1;(j=s[len-i-1]-'0',0<=l<=9)
最后还要减去多算的前导0,即:
g[len-1][0]=g[len-1][0]-cnt1+1;
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long ll;
ll f[20][10],g[20][10],ans[10],l,r,cnt1,cnt2,cnt3; //cnt1:前导0的个数 cnt2:10^i-1 cnt3:s[len-i..len-1]
char s[20];
void count(ll x,int k){
sprintf(s,"%lld",x);
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
int len=strlen(s);
for(int i=0;i<10;i++)f[0][i]=1;
for(int i=0;i<=s[len-1]-'0';i++)g[0][i]=1;
cnt2=1; cnt1=1; cnt3=0;
for(int i=1;i<len;i++){
cnt3=(s[len-i]-'0')*cnt2+cnt3;
cnt2*=10;
cnt1=cnt2+cnt1;
for(int j=0;j<10;j++){
for(int l=0;l<10;l++)f[i][l]+=f[i-1][l];
f[i][j]+=cnt2;
}
for(int j=0;j<s[len-i-1]-'0';j++){
for(int l=0;l<10;l++)
g[i][l]+=f[i-1][l];
g[i][j]+=cnt2;
}
for(int j=0;j<10;j++)
g[i][j]+=g[i-1][j];
g[i][s[len-1-i]-'0']+=cnt3+1;
}
g[len-1][0]=g[len-1][0]-cnt1+1;
for(int i=0;i<10;i++)ans[i]+=k*g[len-1][i];
}
int main(){
scanf("%lld%lld",&l,&r);
count(r,1);
count(l-1,-1);
for(int i=0;i<9;i++)printf("%lld ",ans[i]);
printf("%lld\n",ans[9]);
return 0;
}