788 Dominating Numbers
原题链接:https://projecteuler.net/problem=788
如果一个正数,有超过一半的数字相同,那么这个数称为 dominating 数。
比如 2022 是一个 dominating 数,而 2021 就不是 dominating 数。
令 表示小于 的 dominating 数的个数,即长度小于等于 的十进制数。题目给出两个测试用例:。
求 。
如果我们有一个函数,给定一个 ,能够返回长度等于 的十进制数中的 dominating 数的个数,那么整个解决方案是
如何计算长度为 的数有多少个 dominating 数呢?首先需要遍历超过半数的数字的个数。
现在问题变成了,一个数长度是 (代码使用了 ),有 个相同的数字,可能性有多少种。这是一个排列组合的问题,我们分三种情况讨论。
第一种,超过半数的数字是零。由于零不能在第一位,那么需要在 个位置选择 个位置放零,其余位置能随便选,每个位置的可能性是 9,计算公式如下。 代码实现如下
// dominating is zero
{
var thisCount = CombinationsCount(D - 1, i);
int remainNDigits = D - i;
thisCount *= Power9[remainNDigits];
thisCount %= Mod;
count += thisCount;
}
第二大类的第二小类是第一个数字不是这个个数超过了一半的数字,那么需要在 个位置选择 个位置放这个数。对于其余 个位置,第一个位置不能是零,所以只有 8 中选择方式,其余位置有 9 中选法。同时,这个数字本身也可以从 1-9 中选择,也有 9 中选法。
现在问题是如何计算 呢?
传统的方式(或者说代码)肯定不适合。第一个原因是 中选择 个,数值比较大,那么计算过程中需要考虑取模,第二个原因是性能,每次都调用一个独立函数计算这个值,由于计算次数相当大,即使这个函数挺快的,累计起来性能也很差。
仔细观察这个题目的特征,每次调用 Count
函数时数都自增了 1,也就是说 从 1 到 2022 进行了遍历。 也是从某个数遍历到 。那么我们可以利用如下公式初始化一个矩阵 count[2022][2022]
,里面存放着组合数待查。而初始化这么小的一个矩阵几乎不消耗什么时间。