世界真的很大
所谓数位DP大概就是针对数字的一类操作
其特征相当之明显就不多说,一般都是问范围内什么数字出现多少次一类
而我们此时就通过直接对范围的数为进行递推或递归
本质上来说,其实更接近于递推或者记忆化搜索吧
看题先:
description
给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次。
input
输入文件中仅包含一行两个整数a、b,含义如上所述。
output
输出文件中包含一行10个整数,分别表示0-9在[a,b]中出现了多少次。
首先对于范围的问题我们一般都是拆成两个前缀和之差
所以问题就转化成了0到n中每个数码出现了多少次
首先考虑,假设n是一个m位数,那么m-1位肯定是可以取完而不受范围影响的,这也是数位DP很重要的一个性质
不考虑0的情况下,在所有m位数里,1到9出现的次数都应该是一样的,比如在所有的两位数,(允许前导0),1到9都各出现20次
而这个次数是可以预处理的
设f(i)表示i位数的次数,那么f(i)=10*f(i-1)+10^(i-1),还是比较好理解,大概就是i-1位数里面有多少次,增加了一位,就有10倍那么多,在加上新增加的这一位,有10^(i-1)这么多
只是0有点特别,需要递推处理
那么就可以很快得到m-1位数各个出现的次数,只需要考虑n多出的部分就行了。
而对于多出的部分的每一个数位i,都可以由f(i-1)*n第i位的数字得到,这也是由于只要这一位不等于n这一位的数字,那对于后面的数就无限制,递推的加上去就好
只是需要注意第m位是不允许有前导0的,最后要减去多的部分
完整代码:
#include<stdio.h>
#include<cstring>
using namespace std;
typedef long long dnt;
dnt a,b;
dnt f[20],ten[20],cnt[2][20];
void DP(dnt n,int K)
{
if(n==0) return ;
int m=0,num[15];
dnt res=n;
memset(num,0,sizeof(num));
while(n)
{
num[++m]=n%10;
n/=10;
}
for(int i=1;i<m;i++) cnt[K][0]+=f[i-1]*9;
for(int i=1;i<10;i++) cnt[K][i]=f[m-1];
for(int i=m;i;i--)
{
res-=num[i]*ten[i-1];
for(int j=0;j<num[i];j++) cnt[K][j]+=ten[i-1];
for(int j=0;j<10;j++) cnt[K][j]+=f[i-1]*num[i];
cnt[K][num[i]]+=res+1;
}
for(int i=0;i<10;i++) cnt[K][i]-=f[m-1];
cnt[K][0]-=ten[m-1];
}
int main()
{
ten[0]=1;
for(int i=1;i<=15;i++)
{
ten[i]=ten[i-1]*10;
f[i]=f[i-1]*10+ten[i-1];
}
scanf("%lld%lld",&a,&b);
DP(a-1,0);
DP(b,1);
for(int i=0;i<9;i++)
printf("%lld ",cnt[1][i]-cnt[0][i]);
printf("%lld",cnt[1][9]-cnt[0][9]);
return 0;
}
/*
Whoso pulleth out this sword from this stone and anvil is duly born King of all England
*/
嗯,就是这样