[ZJOI2010]数字计数
题意
给定两个正整数 \(a\) 和 \(b\),求在 \([a,b]\)中的所有整数中,每个数码(digit)各出现了多少次。
输入
1 99
输出
9 20 20 20 20 20 20 20 20 20
算法
最近好像喜欢上数位DP了
这是一个三维的DP
设\(dp[i][j][k]\) 设从后往前填到第\(i\)位 这一位上的数字是\(j\) \(k\)这个数字出现的次数
初始化代码
void init(){
for(ll i = 0;i <= 9;i++)
dp[1][i][i] = 1;
for(ll i = 2;i <= 12;i++)//第几位
for(ll now = 0;now <= 9;now++){//当前的数字
dp[i][now][now] = pow(10,i - 1);//先计算现在填的这位的贡献
for(ll k = 0;k <= 9;k++)//上一位的数字
for(ll j = 0;j <= 9;j++)//数字
dp[i][now][j] += dp[i - 1][k][j];
}
}
我比较习惯在数位DP的时候把DP值求出来 然后根据要求的数字来求解
对于指定的数字求出答案的细节看下面的代码
代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#define ll long long
using namespace std;
ll dp[13][10][10],ans[10],tans[10],a[13],an,bn;
void init(){
for(ll i = 0;i <= 9;i++)
dp[1][i][i] = 1;
for(ll i = 2;i <= 12;i++)
for(ll now = 0;now <= 9;now++){
dp[i][now][now] = pow(10,i - 1);
for(ll k = 0;k <= 9;k++)
for(ll j = 0;j <= 9;j++)
dp[i][now][j] += dp[i - 1][k][j];
}
}
void solve(ll x){
ll cnt = 0;
while(x){
a[++ cnt] = x % 10;
x = x / 10;
}//惯用分解数字
memset(ans,0,sizeof(ans));
for(ll i = 1;i < cnt;i++){//先统计位数比x少的数
for(ll j = 1;j <= 9;j++)
for(ll k = 0;k <= 9;k++)
ans[k] += dp[i][j][k];
}
for(ll i = 1;i < a[cnt];i++)//位数同x一样但是最高位比x最高位小的
for(ll k = 0;k <= 9;k++)
ans[k] += dp[cnt][i][k];
for(ll i = cnt - 1;i >= 1;i--){//位数同x一样最高位也和x一样的
for(ll j = 0;j < a[i];j++)
for(ll k = 0;k <= 9;k++)
ans[k] += dp[i][j][k];
for(ll p = cnt;p > i;p--)
ans[a[p]] += a[i] * pow(10,i - 1);
}
return ;
}
int main(){
scanf("%lld%lld",&an,&bn);
init();
solve(bn + 1);//solve这个函数求的是[0,x)的答案
for(ll i = 0;i <= 9;i++)
tans[i] += ans[i];
solve(an);
for(ll i = 0;i <= 9;i++)
tans[i] -= ans[i];
for(ll i = 0;i <= 9;i++)
cout<<tans[i]<<" ";
return 0;
}