类型:数位DP
传送门:>Here<
题意:给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次
解题思路
本题基本思路和上题很相似,只是前导零的问题比较麻烦啊……
定义状态:$dp[i][j][k]$表示$i$位数,开头为$j$,数码$k$的出现次数。很容易想到转移方程$$dp[i][j][k] = \sum\limits_{0 \leq p \leq 9} dp[i-1][p][k]$$并且特判$j=k$的情况,此时应当额外加上$10^{i-1}$
如果还是按照正常的分层法去统计就会出现一些问题:例如,数字$23$可以存在于$dp[2][2][...]$,也可以存在于$dp[3][0][...]$。如果正常统计将为产生大量的重复——导致0的数量太大,这就是刚才所说的前导零带来的麻烦。而造成这种麻烦的却只在第一位,后面的位数又没有任何影响了。因此我们先抛开第一位统计其他所有数,也就是统计所有$0 \rightarrow len-1$位的数。并且注意在统计这些数时,只能取$dp[i][1 \rightarrow 9][...]$而不能取0。因为想象一下在上一题中我们取0的意义——当前这一位取0意味着位数减少了1. 而目前我们已经在枚举位数了,因此0作为开头的存在完全没有意义。
然后我们强制第一位不能取0,并且开始做$len$位数的统计。但是此时我们又需要取到0了,因为这个时候取0是有意义的。并且统计位数的时候,我们不能忽略限制的那一位。我们正常的时候会扫$0 \rightarrow digit[i]-1$,那么$digit[i]$作为答案什么时候累积呢?因此我们每次进入下一位(也就是确定digit[i])的时候进行累积:要加上余下的那么多次——$x \% 10^{i-1} + 1$
Code
和上一题不同,这里统计的就不是小于N的了,而是能够包括N。
/*By DennyQi 2018.8.13*/ #include <cstdio> #include <queue> #include <cstring> #include <algorithm> #define r read() #define Max(a,b) (((a)>(b)) ? (a) : (b)) #define Min(a,b) (((a)<(b)) ? (a) : (b)) using namespace std; typedef long long ll; #define int long long const int MAXN = 10010; const int MAXM = 27010; const int INF = 1061109567; inline int read(){ int x = 0; int w = 1; register int c = getchar(); while(c ^ '-' && (c < '0' || c > '9')) c = getchar(); if(c == '-') w = -1, c = getchar(); while(c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar(); return x * w; } int N,M,num; int digit[16],dp[16][14][14],ans1[12],ans2[12],pw[16]; inline void Init(){ pw[0] = 1; for(int i = 1; i <= 12; ++i){ pw[i] = pw[i-1] * 10; } for(int j = 0; j <= 9; ++j){ dp[1][j][j] = 1; } for(int i = 2; i <= 12; ++i){ for(int j = 0; j <= 9; ++j){ for(int k = 0; k <= 9; ++k){ if(j == k){ dp[i][j][k] += pw[i-1]; } for(int p = 0; p <= 9; ++p){ dp[i][j][k] += dp[i-1][p][k]; } } } } } inline void cul(int x){ int y = x; num = 0; while(y > 0){ digit[++num] = y % 10; y /= 10; } for(int i = 1; i < num; ++i){ for(int j = 1; j <= 9; ++j){ for(int k = 0; k <= 9; ++k){ ans1[k] += dp[i][j][k]; } } } for(int i = num; i; --i){ for(int j = 0; j < digit[i]; ++j){ if(i == num && j == 0) continue; for(int k = 0; k <= 9; ++k){ ans1[k] += dp[i][j][k]; } } ans1[digit[i]] += (x%pw[i-1] + 1); } } #undef int int main(){ #define int long long N = r, M = r; Init(); cul(M); for(int i = 0; i <= 9; ++i) ans2[i] = ans1[i]; memset(ans1,0,sizeof(ans1)); cul(N-1); for(int i = 0; i <= 9; ++i) printf("%lld ", ans2[i]-ans1[i]); return 0; }