BZOJ1833: [ZJOI2010]count 数字计数
BZOJ1833: [ZJOI2010]count 数字计数
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。
题解Here!
一眼看出是数位$DP$。
然后也一眼看出不会做。。。
一脸无奈。。。
开始乱搞。。。
然后真的把数位$DP$搞出来了。。。
开森!
设$dp[i][j][k]$表示一个长度为$i$的数,其中最高位是$j$,$kk$这个数码一共出现的次数。
然后我们需要枚举当前位数,当前最高位填什么数,次高位填什么数,之后转移一下就好了。
转移方程长这个样:
$$dp[i][j][p]=\sum_{k=0}^9dp[i-1][k][p]$$
这不就做完了
吗?
没有!
我们填上的最高位可是出现了很多次,但是一次都没有被记录进去。。。
这不就尴尬了?
好办,我们让剩下的$i-1$为随意选择只考虑当前最高位上出现的数就好了。
因为那些随意选择的数码已经被计入答案了,所以这么做是对的。
于是根据乘法原理,还有:
$$dp[i][j][j]+=10^{i-1}$$
之后就是数位$DP$的板子了。
注意:$slove(x)$求的是$[0,x)$满足条件的数的个数!
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #define MAXN 15 using namespace std; int bit[MAXN]; long long A,B; long long ans[MAXN][2],dp[MAXN][MAXN][MAXN]; inline long long read(){ long long date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } long long mexp(long long a,long long b){ long long s=1; while(b){ if(b&1)s*=a; a*=a; b>>=1; } return s; } void solve(long long n,int d){ int len=0; memset(dp,0,sizeof(dp)); memset(bit,0,sizeof(bit)); while(n){ bit[++len]=n%10; n/=10; } for(int i=0;i<=9;i++)dp[1][i][i]=1; for(int i=2;i<=len;i++) for(int j=0;j<=9;j++){ for(int k=0;k<=9;k++) for(int now=0;now<=9;now++) dp[i][j][now]+=dp[i-1][k][now]; dp[i][j][j]+=mexp(10,i-1); } for(int i=1;i<len;i++) for(int j=1;j<=9;j++) for(int k=0;k<=9;k++) ans[k][d]+=dp[i][j][k]; for(int i=1;i<bit[len];i++) for(int j=0;j<=9;j++) ans[j][d]+=dp[len][i][j]; for(int i=len-1;i>=1;i--){ for(int j=0;j<bit[i];j++) for(int k=0;k<=9;k++) ans[k][d]+=dp[i][j][k]; for(int j=len;j>i;j--)ans[bit[j]][d]+=1LL*bit[i]*mexp(10,i-1); } } void work(){ A=read();B=read(); solve(B+1,0);solve(A,1); for(int i=0;i<=9;i++)printf("%lld ",ans[i][0]-ans[i][1]); printf("\n"); } int main(){ work(); return 0; }