数位 dp 学习心得
感觉非常牛逼。最牛逼的是很多情况下要去掉前导零。
去掉前导零的方法通常是先忽略前导零的约束,最后再容斥掉有多少0。
Luogu P2602 数字计数
来自【详细解释】数字计数 ZJOJ p2602 一道练习数位DP的好题 - moye到碗里来 的博客 - 洛谷博客 (luogu.com.cn)
那么我们首先看题,对于这道题我一开始以为很水,但是当我仔细去读题之后发现事情没那么简单。其中对于这道题(递推做法)最大的难点是难以找出递推式子(废话,你写递推就只有这点难了)。为啥,因为你很难想到怎样求出第几位它的数字又多少,因为不能有前导0。但是我们发现如果不考虑是否有前导0的话,那么这道题就似乎有递推公式。
\(f[i]\) 代表在有 i 位数字的情况下,每个数字有多少个。如果不考虑前导0,你会发现对于每一个数,它的数量都是相等的,也就是 \(f[i]=f[i-1]\times 10+10^{(i-1)};\)
( 这里我推荐使用打表+大眼观察法 )
然而这个公式推出来后,你就会面临第二个难题,怎么推出我想要的答案?
我们先设数字为ABCD
看A000
,如果我们要求出它所有数位之和,我们会怎么求?
鉴于我们其实已经求出了0
`9`,`0`99
,0
~999
。。。上所有数字个数( \(f[i]\) , 且没有考虑前导零)我们何不把这个A000
看成0000
`1000`2000
...A000
对于不考虑首位每一个式子的数字的出现个数为 \(A\times f[3]\) 。加上首位出现也就是小于 A
每一个数都出现了 \(10^3\) 次,再加上,我们就把 A000
处理完了。
这样你以为就把第一位处理完了?不不不,首位 A
还出现了 \(BCD+1\)次呢,也就是从A000
~ABCD
,这个A
还出现了 \(BCD+1\) 次,所以再加上这些才行呢。那么你发现,我们成功把首位代表的所有数字个数求出来了,剩下的求解与 A
完全没有任何关系,只是 BCD
的求解,于是我们发现我们已经把一个大问题,化成了一个个小问题,也即是,对于一个这样 \(n\) 位的数,把他一位位的分离开来。
当然你还需要处理前导零。你会发现它一定是 0000
, 0001
, 0002
... 0012
,0013
... 0101
, 0102
... 0999
这样的数,一共出现了 \(10\times(i-1)+10\times(i-2)+... 10+i\) (i表示数字位数,最后加 \(i\) 是因为0000
也被统计了),让 0
的统计减去这个值,那么恭喜你这道题做完了。
\(\frak{CODE}\)
#include<bits/stdc++.h>
using namespace std;
#define N 13
#define ll long long
ll teno[N],temu[N];//teno代表小于10^i的所有数的数码出现次数,temu代表10^i
ll L,R,ans1[11],ans2[11];
void solve(ll n,ll *ans){
int a[N],len=0;
while(n>0){
a[++len]=n%10;
n/=10;
}//a就是n的每一位数码,a[len]位最大,a[0]位最小
for(int i=len;i>=1;--i){
for(int j=0;j<=9;++j)
ans[j]+=teno[i-1]*a[i];//1XXX 2XXX 3XXX 4XXXX 5XXX 6XXX 的 XXX 部分数字出现次数
for(int j=0;j<a[i];++j)
ans[j]+=temu[i-1];//AXXX 的 A 部分数字出现次数
ll tmp=0;
for(int j=i-1;j>=1;--j)
tmp=tmp*10+a[j]; //(a[i])BCD 的 a[i] 在 (a[i])000 到 (a[i])BCD 之间出现了多少个a[i]
ans[a[i]]+=tmp+1; //(a[i])000 还没有统计
}
for(int i=len-1;i>=1;--i){
ans[0]-=(len-i)*temu[i-1]*9;//00AB 这种数字要删掉先导0
} //10-99都只用删(len-2)位,100-999都只用删(len-3)位,以此类推。
ans[0]-=len; //0000 还没有删除
return;
}
int main(){
temu[0]=1;
scanf("%lld %lld",&L,&R);
for(int i=1;i<N;++i){
teno[i]=teno[i-1]*10+temu[i-1];
temu[i]=temu[i-1]*10;
}
solve(R,ans1);solve(L-1,ans2);
for(int j=0;j<=9;++j)
printf("%lld ",ans1[j]-j[ans2]);
return 0;
}