BZOJ1833 count 数字计数 数位dp基础题

链接:

count 数字计数

思路:

1e12的范围使得“暴力枚举每个数,然后对一个数进行分解”的暴力做法显然走不通。

其实这题是最基础的数位dp,甚至没有限定特征,直接贴板子就好。

问[l,r]区间所有数位出现的次数,可以转化为求[0,l-1]和[0,r]两个区间数位出现的次数,对应数位相减即可。(消除下限)

考虑[0,n],设dp[i][j][k]为长度为i,首位为j的所有数字中k出现的次数,则存在下列等式:

dp[i][j][j]=1(i=1,0\leq j\leq 9)

dp[i][j][k]=\sum_{j=0}^{9}dp[i-1][j][k]+10^{i-2}(i\geq 2)

由此可以初始化dp[i][j][k]数组

求[0,n]里面各数码出现次数,可以分成两部分

以n=321为例,分成最高位(百位数)为0和最高位(百位数)不为0来分类,

百位数为0时,i=1或i=2,此时所有dp[i][j][k]都应该算上。

百位数不为0时,发现次高位(十位)有限制,只能取0,1,2,且取2时个位数又有限制,只能取0,1。通过循环可以模拟这个限制,详情看代码实现。

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
static const int maxn = 15;
static const int INF = 0x3f3f3f3f;
static const int mod = (int)1e9 + 7;
static const double eps = 1e-6;
static const double pi = acos(-1);
 
void redirect(){
    #ifdef LOCAL
        freopen("test.txt","r",stdin);
    #endif
}
 
ll l,r,ans[maxn],decimal[maxn],d[maxn],dp[maxn][maxn][maxn];
 
ll solve(ll x,int flag){
    int cnt = 0;//计位 
    ll bak = x;//*.bak为备份文件,取此意 
    memset(d,0,sizeof(d));
    while(x){
        d[++cnt] = x % 10;
        x /= 10;
    }
    //最高位为0时
    for(int i = 1;i < cnt;i++)
    for(int j = 1;j <= 9;j++)
    for(int k = 0;k <= 9;k++)
    ans[k] += dp[i][j][k] * flag;
    //最高位不为0时
    int tmp = cnt;
    while(tmp){
        for(int i = 0;i < d[tmp];i++){
            if(!i && tmp == cnt)continue;
            for(int j = 0;j <= 9;j++)
            ans[j] += dp[tmp][i][j] * flag;
        }
        ans[d[tmp]] += (bak % decimal[tmp] + 1)*flag;
        tmp--;
    }
}

int main(){
    redirect();
    //计算10^n 
    decimal[1] = 1;
    for(int i = 2;i <= 13;i++)decimal[i] = decimal[i-1]*10;
    //一位数出现次数=1 
    for(int i = 0;i <= 9;i++)dp[1][i][i]=1;
    //比10e12稍大 
    for(int i = 2;i <= 13;i++)//数字长度 
    for(int j = 0;j <= 9;j++)//最高位数字 
    for(int k = 0;k <= 9;k++){//枚举数码 
        for(int z = 0;z <= 9;z++)dp[i][j][z] += dp[i-1][k][z];//对应(sigma{dp[i-1][j][k]}) 
        dp[i][k][k] += decimal[i-1];//对应10^(i-2) 
    }
    scanf("%lld %lld",&l,&r);
    solve(r,1);
    solve(l-1,-1);
    for (int i = 0;i <= 9;i++)
    printf("%lld%c",ans[i],i==9?'\n':' ');
    return 0;
}

猜你喜欢

转载自blog.csdn.net/krypton12138/article/details/86890811