题目来源:
HihoCoder1187题目要求:
题目要求给定一个数字n,在所有不大于n的数字中,找出因数最多的数字。如果有多个数字有相同数目的因数,那么输出数值最小的一个。
解答:
本题的难度在于数据的范围很大。如果采用暴力的方式解答的话,一定会超时。因此需要考虑一些高效率的算法。
本题的解法基于以下的定理:对于一个数,要么它是一个素数,要么它可以分解为多个素数的乘积形式,并且如果不考虑因数的顺序,这种分解方式一定是唯一的(证明不会 )。即任何数字的分解质因数的结果都是唯一的。于是对于任意的数字n,都可以写成如下的形式:
其中:p1 p2 ... pm都是素数。由于等式右边是m个数字相乘,因此取出上面右边式子其中的一部分得到的结果一定是n的因数。
由于每一个数字的分解质因数的结果是唯一的,因此,不同的素数的组合乘积也一定是不同的,而上式中右边的数字均为素数。因此基于这样的结论,就可以得到数字n的因数的数目C(n)为:
这可以理解为一个构造素数乘积组合的过程,首先考虑p1,可以不选择p1,也可以选择1个p1,选择2个...选择l1个,因此共有(l1+1)中选择方法。其他素数的选择方式也类似,因此不同的素数乘积组合就有:(l1+1)(l2+1)....(lm+1)种。而每一种都确定n的唯一的一个因数。
到这里,本题的思路就比较清晰了,枚举素数的乘积的组合,然后在保证乘积不大于给定的n的时候,找到因数数目最多的数据。类似于背包问题的思路,首先考虑最小的素数2,考虑不选择2,选择1个,选择2个....直到2的k次方大于n为止。当确定了2的一个选择策略时,再递归考虑下一个素数3,以此类推。当递归结束是,一定能得到本题的解。
但是,这样的解法同样会超时,因此还需要进行一步重要的剪枝。这里要注意:如果有多个数字有最多的因数数目,这时要选择数值最小的一个。因此本题的最优解一定满足下面的条件:
假设题目中的最优解是x,把x分解质因数的结果如下:
如果p1 p2 ... ps 是升序排列的话,那么就可以得到这样的结论:
k1≥k2≥....≥ks
即,这些素数的指数是降序排列的。证明很简单,交换上式中任意两个素数因子的指数得到x',那么根据上面的计算公式,x'和x拥有的因数的数目是相同的,但x'一定是大于x的,x'就一定不是我们要找的解。
这样在进行背包枚举时,每进入一轮递归,只需要枚举比上一轮递归时选择的指数小的数字即可,计算效率也可以大大提升。
输入输出格式:
输入:输入仅有一行,给出数字n
输出:输出也仅有一行,输出不大于n的数字中因数数目最多的数字,有多个解话,输出最小的一个。
数据范围:
0≤n≤10000000000000000 (10^16)
程序代码:
由于每一个数字的分解质因数的结果是唯一的,因此,不同的素数的组合乘积也一定是不同的,而上式中右边的数字均为素数。因此基于这样的结论,就可以得到数字n的因数的数目C(n)为:
到这里,本题的思路就比较清晰了,枚举素数的乘积的组合,然后在保证乘积不大于给定的n的时候,找到因数数目最多的数据。类似于背包问题的思路,首先考虑最小的素数2,考虑不选择2,选择1个,选择2个....直到2的k次方大于n为止。当确定了2的一个选择策略时,再递归考虑下一个素数3,以此类推。当递归结束是,一定能得到本题的解。
但是,这样的解法同样会超时,因此还需要进行一步重要的剪枝。这里要注意:如果有多个数字有最多的因数数目,这时要选择数值最小的一个。因此本题的最优解一定满足下面的条件:
假设题目中的最优解是x,把x分解质因数的结果如下:
k1≥k2≥....≥ks
即,这些素数的指数是降序排列的。证明很简单,交换上式中任意两个素数因子的指数得到x',那么根据上面的计算公式,x'和x拥有的因数的数目是相同的,但x'一定是大于x的,x'就一定不是我们要找的解。
这样在进行背包枚举时,每进入一轮递归,只需要枚举比上一轮递归时选择的指数小的数字即可,计算效率也可以大大提升。
输入输出格式:
输入:输入仅有一行,给出数字n
输出:输出也仅有一行,输出不大于n的数字中因数数目最多的数字,有多个解话,输出最小的一个。
数据范围:
0≤n≤10000000000000000 (10^16)
程序代码:
/****************************************************/
/* File : hiho_week_89.cpp */
/* Author : Zhang Yufei */
/* Date : 2016-03-16 */
/* Description : HihoCoder ACM program. (submit:g++)*/
/****************************************************/
/*
* Update log:
* Create by Zhang Yufei in 2016-03-16.
*/
#include<stdio.h>
/*
* The prime numbers list.
*/
int prime[13] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41};
// The input.
long long n;
// Record the result to output.
long long result;
// Record the number of devisors.
long long max_devisors;
/*
* This function computes the number of devisiors of the
* given n.
* Parameters:
* @num: The number n to opearte.
* @dice: The dice of the last prime.
* @index: The current index of prime number.
* @devisors: The current number of devisors.
* Returns:
* None.
*/
void compute(long long num, int dice, int index, long long devisors);
/*
* The main program.
*/
int main(void) {
scanf("%lld", &n);
result = -1;
max_devisors = -1;
compute(1, 64, 0, 1);
printf("%lld\n", result);
return 0;
}
/*
* This function computes the number of devisiors of the
* given n.
* Parameters:
* @num: The number n to opearte.
* @dice: The dice of the last prime.
* @index: The current index of prime number.
* @devisors: The current number of devisors.
* Returns:
* None.
*/
void compute(long long num, int dice, int index, long long devisors) {
if(max_devisors == -1 || max_devisors < devisors) {
max_devisors = devisors;
result = num;
}
if(max_devisors == devisors && num < result) {
result = num;
}
if(index == 13) {
return;
}
int i = 1;
long long c = prime[index];
while(c * num <= n && i <= dice) {
compute(c * num, i, index + 1, devisors * (i + 1));
i++;
c *= prime[index];
}
}