题意

题目本身没有任何背景描述和抽象化,直接题目本身就是题意

所有的长度为n的数中,各个位上的数字之和为m的这些数的和是多少呢。给定n和m,求这些数的和

nnn 最大是 6

方法

实现

在算法比赛内比较早的一个估计计算时间的概念是10810^8108次加法为111秒。(虽然随着硬件升级以及编译器的一些优化可能现在已经快了不少

那么这道题 所有可能的值最大10610^6106, 对于一个给定的六位以内的数,计算其各个位的和,需要大约6*加法/取模/除法常数的运算量, 还是常数倍。

代码

class Solution {
public:
    int sumdigit(int v){
        int r = 0;
        while(v){
            r+=v%10;
            v/=10;
        }
        return r;
    }
    /**
     * 返回这样的数之和
     * @param n int整型 数的长度
     * @param m int整型 各个为之和
     * @return long长整型
     */
    long long sum(int n, int m) {
        long long ans = 0;
        // write code here
        int range[] = {0,10,100,1000,10000,100000,1000000};
        for(int i = range[n-1];i<range[n];i++){
            if(sumdigit(i) == m)ans+=i;
        }
        return ans;
    }
};

复杂度分析

时间复杂度: 对于一个给定的六位以内的数,计算其各个位的和,需要常数倍数的运算量,其中nnn是位数, 所以总的时间复杂度为O(n10n)O(n\cdot 10^n )O(n10n)

空间复杂度: 空间只需要记录当前 的总和,常数大小的空间,所以空间复杂度为 O(1)O(1)O(1)

贡献统计/dfs+记忆化

解这道题完全用不到这个方法,但是如果n的值更大,无法通过枚举所有值来实现的时候,可以考虑用贡献统计和dfs来完成

注意到一个nnn位数,只有首位不能为零。考虑首位以外的进行深度搜索

f(总和,位数) = 方案数

那么有关系式

f(cnt,sz)=i=0..9f(cnti,sz1)f(cnt,sz) = \sum_{i = 0..9}{f(cnt-i,sz-1)}f(cnt,sz)=i=0..9f(cnti,sz1)

我们可以通过cnt<0,cnt>9sz,sz==1,sz==0cnt < 0, cnt > 9 * sz, sz==1, sz==0 cnt<0,cnt>9sz,sz==1,sz==0 进行一定的边界判断,通过记忆化mapmapmap 能保证所有状态最多搜一次

这样我能计算出除了首位的剩余的方案数,令它为www

这道题要求的是所有数字的和,

首位在总和的贡献为w10n1w \cdot 首位 \cdot 10^{n-1} w10n1

剩余位置所有值的和是w(m)w \cdot (m-首位) w(m), 剩余位置是等概率期望的,所以每一位的和值是w(m)n1\frac{w \cdot (m-首位) }{n-1}n1w(m), 然后把它们乘上11..111..111..1, 就是总和,其中11..1=99..99=10n11911..1 = \frac{99..9}{9} = \frac{10^{n-1}-1}{9}11..1=999..9=910n11

那么 答案为

ans=i=1..9(10n1if(mi,n1)+w(mi)n110n119)ans = \sum_{i=1..9} (10^{n-1} \cdot i \cdot f(m-i,n-1) + \frac{w\cdot(m-i)}{n-1} \cdot \frac{10^{n-1}-1}{9})ans=i=1..9(10n1if(mi,n1)+n1w(mi)910n11)


以题目的样例数据2,32,32,3为例

首位1, (mi,n1)=(31,21)=(2,1)(m-i,n-1) = (3-1,2-1) = (2,1)(mi,n1)=(31,21)=(2,1)

dfs(cnt,sz)

当前层 第一层 第二层
1 (2,1)
sz == 1 返回1

也就是说明,长度为n=2n=2n=2,总和m=3m=3m=3,的情况下,111放在首位有111种方案

那么首位的对总和的贡献是11101\cdot 1 \cdot 101110, 而首位以外的贡献是1(31)11\frac{1\cdot (3-1)}{1} \cdot 111(31)1


以数据3,43,43,4为例

首位1, (mi,n1)=(41,31)=(3,2)(m-i,n-1) = (4-1,3-1) = (3,2)(mi,n1)=(41,31)=(3,2)

dfs(cnt,sz)

当前层首位 第一层 第二层 第三层
1 (3,2)
0 (3,1)
sz == 1 返回1
1 (2,1)
sz == 1 返回1
2 (1,1)
sz == 1 返回1
3 (0,1)
sz == 1 返回1

也就是说明,长度为n=3n=3n=3,总和m=4m=4m=4,的情况下,111放在首位有444种方案

那么首位的对总和的贡献是141001\cdot 4 \cdot 10014100, 而首位以外的贡献是4(41)211\frac{4\cdot (4-1)}{2} \cdot 1124(41)11

代码

class Solution {
public:
    map<pair<int,int>,long long> cntsz2val; // 记忆化
    long long ways(long long cnt,long long sz){
        if(sz * 9 < cnt)return 0;
        if(cnt < 0) return 0;
        if(sz == 1) return 1;
        if(sz == 0) return cnt == 0;
        if(cntsz2val.count(make_pair(cnt,sz))){
            return cntsz2val[make_pair(cnt,sz)];
        }
        // 当前位置放i
        for(int i = 0;i < 10;i++){
            cntsz2val[make_pair(cnt,sz)] += ways(cnt-i,sz-1); // 状态转移
        }
        return cntsz2val[make_pair(cnt,sz)];
    }
    /**
     * 返回这样的数之和
     * @param n int整型 数的长度
     * @param m int整型 各个为之和
     * @return long长整型
     */
    long long sum(int n, int m) {
        long long ans = 0;
        // write code here
        int range[] = {0,10,100,1000,10000,100000,1000000};
        if(n == 1)return m;
        // 首位
        for(int i = 1;i<10;i++){
            long long w = ways(m-i,n-1);
            ans += i * range[n-1] * w + (m-i) * w / (n-1) * ((range[n-1]-1)/9);
        }
        return ans;
    }
};

这道题如果n对应变大,那么就可以使用这种方法,因为m9nm\leq 9nm9n, 所以我们n=100n=100n=100 也是能在1s内完成的

复杂度分析

空间复杂度: 我们为了减少dfs中的重复计算,有状态[剩余个数][剩余位数],所以空间复杂度O(mn)O(mn)O(mn)

时间复杂度: 对于所有状态我们进行了dfs了,状态转移过程是每次第一个位置放0~10,是常数级别的状态转移,所以总的时间为 转移代价x状态数,也是O(mn)O(mn)O(mn)

知识点

  1. int加法注意溢出问题,用long long 来保存
  2. 大概的1秒能计算多少次,在写代码之前就能估计所使用的算法复杂度是否能满足题目时限,打算法时,有时数据范围小的情况暴力实现即可