[bzoj 1833] [ZJOI2010]count 数字计数:数位DP

来源:互联网 发布:mac版chrome好用吗 编辑:程序博客网 时间:2024/05/21 22:50

题意:求[a, b]之间的所有整数中各个数码出现了多少次。(1<=a<=b<=10^12,a, b是整数)

考虑[1, x)内各个数码出现了多少次。设f[i][j][k]为以j开头的(i+1)位十进制串中k出现的次数,递推。
[1, x)内的数可分为三类:
1. 位数少于x。
2. 最高位小于x。
3. 和x有公共的前缀。
分类统计,第二、三类可以合并。设现在考虑从高往低第k位,则f数组包含k及其右边的数码,k左边的数码还需要单独计算。

看了Po姐的题解,感觉比我写的要美……

f[i][j][k]可简化为f[i](依然允许前导0),因为每个数码出现的次数相同——只是一些字符串罢了。有递推式f[i] = f[i-1]*10 + 10^i,其中f[i-1]*10是低i位中某数码的出现次数(用0~9共10个数码作为最高位),10^i是它在最高位的出现次数。
位数少于x的数,枚举1~9作为最高位。最高位中每个数码出现10^i次,低i位中每个数码出现f[i-1]*9次。
和x有公共前缀的数,自由的非公共部分每个数码出现f[i-1]次,公共部分和非自由非公共部分一起暴力统计。公共部分可以每次计算,也可以像我的代码这样放在一起考虑,如x=1234,则1在公共部分出现234次,2在公共部分出现34次,3在公共部分出现4次。
于是又写了一个版本。

#include <cstdio>#include <cstring>using namespace std;const int MAX_N = 13;typedef long long ll;// f[i][j][k]: 以j打头的(i+1)位十进制串含多少个k// g[i][j] = Sigma(f[i][*][j])ll p[MAX_N], f[MAX_N][10][10], g[MAX_N][10], v[2][10];void cal(ll x, ll a[10]){    ll b[MAX_N];    int n = 0;    for (ll y = x; y; y /= 10)        b[n++] = y % 10;    for (int i = 0; i < n-1; ++i)        for (int j = 0; j < 10; ++j)            a[j] += g[i][j] - f[i][0][j];    for (int i = n-1; i >= 0; --i)        for (int j = (i == n-1); j < b[i]; ++j)            for (int k = 0; k < 10; ++k)                a[k] += f[i][j][k];    for (ll i = 1, y = b[0]; i < n; y = y + b[i]*p[i], ++i)        a[b[i]] += y;}int main(){    ll a, b;    scanf("%lld %lld", &a, &b);    for (int i = 0; i < 10; ++i)        f[0][i][i] = g[0][i] = 1;    p[0] = 1;    for (int i = 1; i < MAX_N; ++i) {        p[i] = p[i-1]*10;        for (int j = 0; j < 10; ++j) {            f[i][j][j] = p[i];            for (int k = 0; k < 10; ++k) {                f[i][j][k] += g[i-1][k];                g[i][k] += f[i][j][k];            }        }    }    cal(b+1, v[0]);    cal(a, v[1]);    for (int i = 0; i < 10; ++i)        printf("%lld%c", v[0][i]-v[1][i], " \n"[i == 9]);    return 0;}
#include <cstdio>using namespace std;typedef long long ll;const int MAX_N = 13;ll f[MAX_N], p[MAX_N], v[2][10];void cal(ll x, ll a[10]){    ll b[MAX_N];    int n = 0;    for (ll y = x; y; y /= 10)        b[n++] = y % 10;    for (int i = 0; i < n-1; ++i)        for (int j = 0; j < 10; ++j)            a[j] += (j ? p[i] : 0) + (i ? f[i-1]*9 : 0); // 最高位,低i位    for (int i = n-1, c; c = 0, i >= 0; --i) {        for (int j = (i==n-1); j < b[i]; ++c, ++j)            a[j] += p[i];        for (int j = 0; j < 10; ++j)            a[j] += i ? c*f[i-1] : 0;    }    for (ll i = 1, y = b[0]; i < n; y += p[i]*b[i], ++i)        a[b[i]] += y;}int main(){    ll a, b;    scanf("%lld %lld", &a, &b);    p[0] = f[0] = 1;    for (int i = 1; i < MAX_N; ++i) {        p[i] = p[i-1]*10;        f[i] = f[i-1]*10 + p[i];    }    cal(b+1, v[0]);    cal(a, v[1]);    for (int i = 0; i < 10; ++i)        printf("%lld%c", v[0][i] - v[1][i], " \n"[i == 9]);    return 0;}
0 0
原创粉丝点击