bzoj 1833: [ZJOI2010]count 数位dp

题目:

给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次。

题解

\(f[i][j]\)表示长度为\(i\)的所有合法数字中有多少数码\(j\)
\(g[i][j]\)表示长度为\(i\)的可有前导零的数字中有多少数码\(j\)
然后恶心不想说了.

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
inline void read(ll &x){
    x=0;static char ch;static bool flag;flag = false;
    while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
    while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
}
#define rg register int
#define rep(i,a,b) for(rg i=(a);i<=(b);++i)
#define per(i,a,b) for(rg i=(a);i>=(b);--i)
const int maxn = 22;
ll f[maxn][maxn],g[maxn][maxn];
int a[maxn],cnt;
inline void init(int n){
    rep(i,0,9){
        g[1][i] = 1;
        if(i) f[1][i] = 1;
    }
    ll pw = 1;
    rep(i,2,n){
        pw *= 10;
        rep(j,0,9){
            g[i][j] = 10*g[i-1][j] + pw;
            f[i][j] = 9*g[i-1][j];
            if(j != 0) f[i][j] += pw;
        }
    }
}
ll solve(ll n,int x){
    if(n == 0) return 0;
    int cnt = 0;
    ll ans = 0;
    static int a[maxn];
    while(n){
        a[++cnt] = n % 10;
        if(a[cnt] == x) ++ ans;
        n /= 10;
    }
    ll pw = 1;
    rep(i,1,cnt) pw *= 10;
    ll tmp = 0;
    per(i,cnt,1){
        pw /= 10;
        ans += tmp*a[i]*pw;
        if(i == cnt){
            ans += (a[i] - 1)*g[i-1][x];
            if(x && x < a[i]) ans += pw;
        }else{
            ans += a[i]*g[i-1][x];
            if(x < a[i]) ans += pw;
        }
        if(a[i] == x) ++ tmp;
    }
    rep(i,1,cnt-1) ans += f[i][x];
    return ans;
}
int main(){
    init(13);
    ll l,r;read(l);read(r);
    rep(i,0,9){
        printf("%lld",solve(r,i) - solve(l-1,i));
        if(i != 9) putchar(' ');
        else putchar('\n');
    }
    return 0;
}
posted @ 2017-04-28 10:38  Sky_miner  阅读(150)  评论(0编辑  收藏  举报