链接:
思路:
1e12的范围使得“暴力枚举每个数,然后对一个数进行分解”的暴力做法显然走不通。
其实这题是最基础的数位dp,甚至没有限定特征,直接贴板子就好。
问[l,r]区间所有数位出现的次数,可以转化为求[0,l-1]和[0,r]两个区间数位出现的次数,对应数位相减即可。(消除下限)
考虑[0,n],设dp[i][j][k]为长度为i,首位为j的所有数字中k出现的次数,则存在下列等式:
由此可以初始化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;
}