正文

第38次编程比赛第一题的一个超快速算法2006-08-21 10:55:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/jackin0627/17725.html

分享到:

   第38次编程比赛第一题的一个超快速算法       这个算法计算速度超快,我只是按设计的算法粗糙的实现了一下,当N=4000000000(四十亿)的时候,计算时间不超过100毫秒, 我没有具体测这个时间,只是一运行,马上就会出结果,无需等待.  //////////////////////////////////////////////////////  原题目    在N以内(小于等于N)找出一个数,要求:     1.这个数的约数的个数达到最大,     2.如果有好几个数满足条件1,仅取最小的那个数    说明:   1) 1<N<=2^32-1,每个N的时限是1000ms。内存限制256M,注意使用适当数据类型,以免溢出。    函数原型:  // n: 范围  // result:结果,存放符合条件的那个数  // count:存入存放符合条件的那个数的约数的个数  // arr:存放那个数的所有约数,按照从小到大的顺序  void Search(unsigned long n, unsigned long *result,int *count,unsigned long arr[]);    例:  n=100, 则 result=60,  在100以内,60和90的约数个数同为12个,达到这个范围内所有整数中,其约数个数的最大值,但60比90小,  所有正确答案为60。60共有12个约数:1,2,3,4,5,6,10.12.15.20.30,60.所以count应该存入12,从arr[0]开始,  应该依次写入1,2,3,4,5,6,10.12.15.20.30,60。      ///////////////////////////////////////////////////////////////////////////    在描述这个算法之前,我们先来讨论一下一个问题,      怎么一个数M的因子个数呢?    最容易想到的算法就是拿1~M的所有数都去除M,能整除的就是M的因子,然后再统计出因子的个数,然而这种方法也是最慢的,如果用这种方法去解比赛的那个题目,N比较小是还可以,当N比较大时,估计你我在有生之年都无法看到结果了,呵呵。    这时你也需想到了另一个快一些的方法,就是用互质因子的组合来求因子,过程是这样的    先把M分解质因数,(这个过程不难,也有比较快速的算法)    如:30可分解成2*3*5。那它的因数个数就是2^3个,为什么呢?如下:                       2          3           5        变量           x1         x2          x3         可取的值       1或2       1或3        1或5   (也就是Xi的0次方和Xi的1次方)    当x1,x2,x3取不同值时,X1*X2*X3的各个结果都是30的因子。根据组合的理论,因子的个数一共是2*2*2=8(为什么?呵呵,好好想想);        这时你可能发现了一个问题 ,就是当M分解质因数后,它的质因子有重复的怎么办呢?    如M=60,质因子为2*2*3*5,有两个2,这种情况怎么算呢?    我们只要把2合为一组,情况如下:                           2          3           5        变量           x1         x2          x3         可取的值       1或2或4    1或3        1或5         (X1的可取值不只是2^0和2^1了,多了一个2^2。这个也不难理解吧!)    那么60的因子个数为3×2*2=12了。    总结规律:        把M分解质因数后我们可以得到这样两个序列:            Zi  :  M的互异的质因子(M=60时 Zi为2,3,5)            Yi  :  对就质因子出现的次数(M=60时 Yi为2,1,1)        为了容易理解怎么求M的因数,我们用到了另一个序列        Xi        对Xi每一元素的可取值为Zi^0,Zi^1,Zi^2.....Zi^Yi        也就是可以取Yi+1种不同的值。        让Xi取不同的值,X1×X2*X3........×Xm的结果就构成了M的所有因子,        而因子的总个数也可简单的用  (Y1+1)×(Y2+1)*(Y3+1)......*(Ym+1)来求的.            以上只是介绍了怎么样求M的因子和因子总数,若要解比赛的题目,还需要对(N/2)到N的所有数的因数总数求出来进行比较,才能找出结果.由于N比较大.这个过程也是比较耗时的.(我测了一下.当N=100000(十万)时),要找出结果要用大约20秒,我的程序没有优化,若优化一下,时间也不会太短.        那还有没有更好的算法来解比赛的题目呢?   有!    细心的你可能发现了上面求M的因子的一个很有趣的规律,那就要想求出M的因子的总个数只要用Yi序列和质因数个数就可以了,而不需要其它的,也就是说因子的总个数与M分解的质因数的具体值无关,只与各质因数出现的次数有关.这个发现太重要,它是这个快速算法的核心.    再往下想一步,对于相同Yi序列和相同的,Xi序列的值越小,对应的M也就越小,换个说法,对于相同的M和相同的质因数个数(就是Xi序列的元素个数),Xi越小,对就Yi中的元素的值就会越大,也就是M的因子总数也就越大,所以我们只需要质数中的前几个就可以了.    实际上只用前10个就足够了,也就是对于任意M,(M<=(2^32)-1),分解质因数后的互异质因数(就是相同的质因数只计一次)不会超过10个.因为取最小的10个质数,它们的乘积2*3*5*7...*29已经超过2^32了.    所以我们不再需要把1到N的所有数都计算一遍了,只要使        Zi = 2,3,5,7,....,29    然后给Yi赋不同的值,且要使这个组合所对应的M的值<=N.    在Yi的不同组合中,再来比较,这样我们就舍去了1到N中绝大部分不付合要求的数了,只要比较一小部分就可以了.    绝大部分数被舍去的原因是他的分解质因数的质因数有比较大质数,这是隐含在我们的算法之中的.    而且Zi中的数不是都要用到的.对于一些比较小的数,只要用前面的若干个就行了.这个在算法中的体现是后面几个不用的对应Yi中的值为0.        呵呵,总算说完了,嘴有点笨,不知你有没有明白.        以下是我的程序,程序太过粗糙了,不够优化还有不少Bug,大伙将就着看看吧.            file://运行环境Vc++6.0  // pfan38_2.cpp : Defines the entry point for the console application.// #include "stdafx.h"#include <string.h>#include <math.h> int fac[10]={2,3,5,7,11,13,17,19,23,29};int num[10]={0};int mnum[10];int nfac=0;int max=0;__int64  cur=1;void add(int p){    int sum=1;    __int64 t=1;     for(int i=0;i<p;i++)    {        sum*=num[i]+1;        t*=(__int64)pow(fac[i],num[i]);    }    if(sum>max||(sum==max&&t<cur))    {        memcpy(mnum,num,10*sizeof(int));        nfac=p;        max=sum;        cur=t;    }} void nSearch(__int64 n,int p,__int64 val){    __int64 t;    for(num[p]=1;;num[p]++)    {        t=val*(__int64)pow(fac[p],num[p]);        if(t>n)        {            num[p]--;            if(num[p]==0)                p--;            add(p+1);            break;        }        else if(p==9)        {            add(10);        }        else        {            nSearch(n,p+1,t);        }    }}int parr=0;void getarr(unsigned long arr[],int fac[],int num[],int len,int cur,__int64 val){    __int64 v;    if(len==cur+1)    {        for(int i=0;i<=num[cur];i++)        {            arr[parr++]=(unsigned long)(val*(__int64)pow(fac[cur],i));        }    }    else    {        for(int i=0;i<=num[cur];i++)        {            v=val* (__int64)pow((double)fac[cur],(double)i);            getarr(arr,fac,num,len,cur+1,v);            }    }}void Search(unsigned long n, unsigned long *result,int *count,unsigned long arr[]){    __int64 t;     nSearch(n,0,1);        parr=0;    getarr(arr,fac,mnum,nfac,0,1);    for(int i=0;i<max-1;i++)        for(int j=i+1;j<max;j++)            if(arr[i]>arr[j])            {                t=arr[i];                arr[i]=arr[j];                arr[j]=(unsigned long)t;            }     *result=(unsigned long)cur;    *count=max;} int main(int argc, char* argv[]){    unsigned long res,arr[10240],n;    int cnt,i;     n=4000000000;    Search(n, &res,&cnt,arr);    printf("\n%lu-%lu-%d--------------------------------------\n",n,res,cnt);        for(i=0;i<cnt;i++)    {        printf("[%lu]",arr[i]);    }     return 0;}        

阅读(5368) | 评论(4)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

loading...
您需要登录后才能评论,请 登录 或者 注册