516 5 smooth Totients
5-smooth
数是指质因数不超过5的数。换句话说,质因数只能是 2,3,5。
是指不超过的 的 求和,其中 满足 欧拉函数,Euler's totient function 是一个 5-smooth
数。
,题目给出这个提示是进行一个简单的验证,检查自己的算法/程序是否有问题。
求
次方很大,电脑是 GHz 级别,,所以遍历小于 的每一个数是不现实的。我们要想办法构造出所有满足条件的 ,然后求和。
有很多种写法,下面这种对于我们分析这个题目很有帮助。
如果质因数只有 2,3,5,减一之后是 1,2,4,那么这个 是满足条件的。
质因数能大于5吗?可以。比如 7, 是 6,质因数是 2 和 3,如果这种质因数的次数 只能是 1, 是 0,那么也还是满足要求的。如果多于 1 次, 就会包含比 5 大的因数。再比如 11,41 等都是满足条件的。
这种质数是可以任意相乘的,组合之后仍然满足题意。他们和质因数只有 2 3 5 的数相乘,依旧满足题意。顺便说一下,只有这些质数作为质因数的数也是满足题意的。
如何构造这种质数呢?找到所有质因数是 2 3 5 的数,加一,再判断是不是质数。 让我们先写个程序找到所有小于 且质因数都是 2 3 5 的数。
for (int i = 0; i < Math.Log(max) / Math.Log(2); i++)
{
for (int j = 0; j < Math.Log(max) / Math.Log(3); j++)
{
for (int k = 0; k < Math.Log(max) / Math.Log(5); k++)
{
if (Math.Pow(2, i) * Math.Pow(3, j) * Math.Pow(5, k) <= max)
{
smooths.Add((long)(Math.Pow(2, i) * Math.Pow(3, j) * Math.Pow(5, k)));
}
}
}
}
smooths.Sort();
smooths
进行了排序,其目的后面会提到。
接下来,生成所有大于 5 且满足上述分析的质数。
那么如何对这些质数进行组合呢?普通的组合算法是行不通的。因为candidates
有 500 多个数据,从中任选 5 个就达到了 这个量级,所以需要合适的裁剪。
我的做法是分层,第一层都是 1 个数组成的组合,第二层都是 2 个数组成的组合,依此类推。同时,后一层是从前一层的组合生成的。对于 层的某一个组合, 层就是从 smooths
里面选一个数,然后添加到尾部。smooths
排序的好处有两个,一是找到 层组合的最后一个数,只需要往上追加比它大的数即可,排序可以利用二分查找;二是如果第 个加上去之后,乘积大于 ,后面的数就不用看了。
List<List<List<long>>> tiers = new List<List<List<long>>>();
var firstTier = candidates.Select(p => new List<long>() { p }).ToList();
tiers.Add(firstTier);
while (tiers.Last().Count != 0)
{
var lastTier = tiers.Last();
var currentTier = new List<List<long>>();
tiers.Add(currentTier);
foreach (var combination in lastTier)
{
double product = combination.Aggregate((total, next) => total * next);
int index = Array.BinarySearch(candidates, combination.Last());
for (int i = index + 1; i < candidates.Length; i++)
{
if (product * candidates[i] <= max)
{
currentTier.Add(new List<long>(combination)
{
candidates[i]
});
}
else
{
break;
}
}
}
}
var factors = new List<long>();
foreach (var tier in tiers)
{
foreach (var combination in tier)
{
factors.Add(combination.Aggregate((total, next) => total * next));
}
}
这种质数是可以任意相乘的,组合之后仍然满足题意。
接着处理下一种情况
他们和质因数只有 2 3 5 的数相乘,依旧满足题意。
排序的好处就是可以提前终止。上面代码的最后一句是把基本 case 包含上了。 如果质因数只有 2,3,5,减一之后是 1,2,4,那么这个 是满足条件的。
smooths
里面包含1( 都是 0 的情况),所以循环也包含了前面提到的这种情况。
顺便说一下,只有这些质数作为质因数的数也是满足题意的。
最后,返回结果。
sum
一开始定义为 long
,uint
截断后就是模 的结果。
上述是 2018 年 7 月解题时的思路和具体实现,仔细审视下代码,有很多地方可以优化。
1. tiers
最里面的 List<long>
我们只用到了最后一个数值和它的乘积,那么使用 Tuple<long, long>
保存要的值即可,避免反复拷贝 List<long>
2. factors.Sort();
是不需要的,因为 break
的前提仅是 smooths
有序就好
3. factors
数值都是前面保存的,那么可以不必最后统一 Add
,在生成的地方添加即可
4. 不需要最后统一调用 factors.Add
,那么保存所有的 tiers
也不需要,两个 tier 来回倒腾即可
5. 最后,factors
也是不需要的
经过这些调整,性能提高了 2.5+ 倍,主要贡献是前面两项。