最近在翻看《算法竞赛入门经典》这本书,上面提到一篇关于如何将N!分解为素数的乘积的文章。原题大意如下:
数一个数 2<n<100;将其表示成素数的乘积例如 4! = 2^3 * 3^1; 5! = 2^3 * 3^1 *5^1;求出任意一个数的素数分解式。
下面我们看一下《算法竞赛入门经典》的源码。后面我给出自己的改进解法。
int is_prime(int n)
{
int i;
for(i = 2; i <= sqrt(n); i++ )
{
if((n % i) == 0)
return 0;
}
return 1;
}
void ConstructPrimeArray()
{
int i;
for(i = 2; i < 100; i++)
{
if(is_prime(i))
{
prime[count++] = i;
}
}
}
void GetIndexArray1(int input)
{
int i;
int j = 0;
int m;
for(i = 1; i <= input; i++)
for( j = 0; j < count; j++)
{ m = i;
while(m % prime[j] == 0)
{
IndexArray[j] ++;
m = m / prime[j];
if(j > maxindex) maxindex = j;
}
}
}
上述函数:is_prime,主要判断一个数是否为素数,ConstructPrimeArray主要是获得100以内的素数,并将将其保存在prime数组中,而最重要的函数则是我们的GetIndexArray其主要获得每一个素数的指数,保存在IndexArray数组中(input为我们输入的那个数,count为100以内的素数在prime中的最大下标)。其主要思想是:从1到input的每一项,我们求其和每一个prime模(m%prime)如果是0则表示其是该素数的倍数,对该素数的指数有贡献,(while语句主要是判断对该指数的贡献是几?)最终将的贡献值存储在IndexArray中。maxindex主要是为了确定最大的素数因子的位置。
对于该算法的修改我们主要集中在GetIndexArray中,先给出代码如下:
void GetIndexArray2(int input)
{
memset(IndexArray, 0, sizeof(IndexArray));
int i = 0;
int m;
int ret = 0;
maxindex = 0;
for(i = 0; i < count; i++)
{ m = input;
if(m / prime[i] == 0)
break;
while(m)
{
ret += m / prime[i];
m = m / prime[i];
}
IndexArray[i] = ret;
ret = 0;
maxindex = i;
}
}
我们现在的代码只剩下两个循环,并且减少了很多循环不是吗?
再解释代码之前我先给出《编程之美》中的一道问题。
即求N!十进制结果中末尾0的个数。末尾有几个0则N! = 2^x *3^y*5^z*.........,中Z的值就为几。因为只有5和偶数相乘才会出现0,又偶数的个数肯定比5的个数多所以末尾0的个数与5的指数相等。那么z究竟是多少呢,一个方法就是《算法竞赛入门经典》中给出的,而令一个方法就是:z = [N/5] + [N/(5^2)] + [N/(5^3)] +.......直到[N/(5^n)]为0。那么究竟为什么这个公式成立呢??
首先 N! = 1*2*3*4*5*6*......N 。那么[N/5]就表示从1到N每隔一个5总共有几个数是5的倍数,如N = 26 则 26/5 = 5;表示5,10,15,20,25会贡献一个5,那么我们会想25会贡献2个5啊!的确所以[N/(5^2)] 等于1,将另一个5计算在内。在这个问题中我们只计算了5的指数,而原题中需要计算所有的指数,那么我们只需要遍历一遍所有素数即可。这个就是上述我的改进程序的思想。好了就讲到这吧!!!!