zoukankan      html  css  js  c++  java
  • 数位DP 求K进制下0~N的每个数每位上出现的数的总和

    好久没写博客了,因为感觉时间比较紧,另一方面没有心思,做的题目比较浅也是另一方面。

    热身赛第二场被血虐了好不好,于是决定看看数位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 }
    View Code

    其中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 }
    View Code

    为了验证跑出来的数据对不对,再写一个暴力求[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 }
    View Code

    完整程序:

     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
    ",sum2(0,n,k));
    53         printf("%I64d
    ",check(n,k));
    54     }
    55     return 0;
    56 }
    View Code
  • 相关阅读:
    JVM的生命周期、体系结构、内存管理和垃圾回收机制
    JVM的ClassLoader过程分析
    MySQL Cluster配置概述
    tomcat下bin文件夹下shell文件分析
    Eclipse环境下使用Maven注意事项
    mysql服务器的字符集
    判断文件中是否存在中文字符
    Tomcat/JSP中文编码配置
    Java内存泄露的原因
    Python 开发轻量级爬虫08
  • 原文地址:https://www.cnblogs.com/acmicky/p/3316923.html
Copyright © 2011-2022 走看看