好久没写博客了,因为感觉时间比较紧,另一方面没有心思,做的题目比较浅也是另一方面。
热身赛第二场被血虐了好不好,于是决定看看数位DP吧。
进入正题:
如题是一道经(简)典(单)的数位dp。
第一步,对于数K^n-1这种形式的数,位数为n,它的各个位上,每个数0~K-1出现过的次数是一样的。
于是对于数B=K^n-1,有f(B)=(B+1)*n*(0+1+2+...+K-1)/K=(B+1)*n*(K-1)/2;
程序为:
1 LL sum1(int pre,int n,int k) 2 { 3 LL ret=0; 4 LL pw=1; 5 for(int i=0;i<n;i++) pw*=k; 6 ret=pre*pw+pw*n*(k-1)/2; 7 return ret; 8 }
其中pre在这种情况下为0,pre是什么?我们立刻进入下一步讨论。
第二步,由第一步的结论,我们可以引申一下。为了更形象一点,我们不妨在十进制的情况下讨论。
现在我提出一个问题:如何计算0~49999的数它们各个位上数字之和?(K=10的前提下)
我们根据第一步可以很容易求出[0,9999]=(9999+1)*4*(10-1)/2。
那么还剩下[10000,19999],[20000,29999],[30000,39999],[40000,49999]该怎么求?
仔细观察发现[10000,19999]不过是每个数都比[0,9999]多了一个为1的万位,[20000,29999]不过是每个数都比[0,9999]多了一个为2的万位,[30000,39999]不过是每个数都比[0,9999]多了一个为3的万位,依次类推...就发现了规律。
所以此时这个与后面的数位都无关的万位,我们用i表示,万位之前没有其他的位,所以pre=0(如果对pre有点不理解,看完第三步就知道了),于是对于[i0000,i9999]这样的解就是((pre+i)*10000)+(9999+1)*4*(10-1)/2。
那么,不难得知,求解通式即为((pre+i)*K^n)+(K^n)*n*(K-1)/2。
第三步,基于第一步和第二步的结论,已经可以求出类似于999(K=10),39999(K=10),49999(K=10)的解。
现在又提出一个问题,对于[0,54321]我们怎么解?
当然,先延续之前“区间划分”+“前缀”的思路,先划分为[0,9999],[10000,19999],[20000,29999],[30000,39999],[40000,49999],[50000,54321]。
对于[0,9999],[10000,19999],[20000,29999],[30000,39999],[40000,49999]已经讨论过了,接下来讨论如何求[50000,54321]。
这时把万位的5看作一个前缀,区间就变为了[0,4321],于是只要求前缀pre=5的[0,4321]的解,也就是递归调用第二步的方法,这样就可以求到[0,321],[0,21],[0,1]这样把所有的解相加,就是需要的答案了。
1 LL sum2(int pre,LL n,int k) 2 { 3 if(n<k){ 4 LL ret=0; 5 for(int i=0;i<=n;i++) ret+=pre+i; 6 return ret; 7 } 8 LL tn=n,pw=1,ret=0; 9 int mi=0; 10 while(tn>=k){ 11 pw*=k; 12 mi++; 13 tn/=k; 14 } 15 for(int i=0;i<tn;i++) 16 ret+=sum1(pre+i,mi,k); 17 ret+=sum2(pre+tn,n-tn*pw,k); 18 return ret; 19 }
为了验证跑出来的数据对不对,再写一个暴力求[0,n]的程序,这查错的办法。
1 LL check(int n,int k) 2 { 3 LL ret=0; 4 int t; 5 for(int i=1;i<=n;i++){ 6 t=i; 7 while(t){ 8 ret+=t%k; 9 t/=k; 10 } 11 } 12 return ret; 13 }
完整程序:
1 #include <stdio.h> 2 typedef long long LL; 3 4 LL sum1(int pre,int n,int k) 5 { 6 LL ret=0; 7 LL pw=1; 8 for(int i=0;i<n;i++) pw*=k; 9 ret=pre*pw+pw*n*(k-1)/2; 10 return ret; 11 } 12 13 LL check(int n,int k) 14 { 15 LL ret=0; 16 int t; 17 for(int i=1;i<=n;i++){ 18 t=i; 19 while(t){ 20 ret+=t%k; 21 t/=k; 22 } 23 } 24 return ret; 25 } 26 27 LL sum2(int pre,LL n,int k) 28 { 29 if(n<k){ 30 LL ret=0; 31 for(int i=0;i<=n;i++) ret+=pre+i; 32 return ret; 33 } 34 LL tn=n,pw=1,ret=0; 35 int mi=0; 36 while(tn>=k){ 37 pw*=k; 38 mi++; 39 tn/=k; 40 } 41 for(int i=0;i<tn;i++) 42 ret+=sum1(pre+i,mi,k); 43 ret+=sum2(pre+tn,n-tn*pw,k); 44 return ret; 45 } 46 47 int main() 48 { 49 LL n; 50 int k; 51 while(~scanf("%I64d %d",&n,&k)){ 52 printf("%I64d\n",sum2(0,n,k)); 53 printf("%I64d\n",check(n,k)); 54 } 55 return 0; 56 }