[ZJOI2010] 数字计数

类型:数位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;
}

猜你喜欢

转载自www.cnblogs.com/qixingzhi/p/9468158.html