• 写一个程序,对于一个正整数,输出它所有可能的连续自然数(两个以上)之和的算式


      问题:写一个程序,对于一个正整数,输出它所有可能的连续自然数(两个以上)之和的算式

      分析:

      设输入的数为N;拆分为k个数;拆分后连续数的第一个数为m。则有

      N = m + (m+1) + (m+2) + …… + (m+k-1)

         = k*m + 1+2+3+……+(k-1) 

         = k*m + (1+(k-1))*(k-1)/2 

         = k*m + k*(k-1)/2

      一种简单的方法,因为m与k必然小于N,只要循环遍历m与k可能取的值,就可以得到N的可能拆分。

    #include <iostream>
    using namespace std;
    int main(int argc, char** argv)
    {
        int m;//起始的数
        int k;//分的项数
        int num;//要拆分的数
        cout<<"请输入要拆分的数:"<<endl;
        while(cin>>num)
        {
            for(m=1;m<num;m++)
            {
                for(k=2;k<num;k++)
                {
                    int tmp = k*m+(k-1)*k/2;
                    if(tmp==num)
                    {
                        cout<<"找到一个拆分组合:";
                        for(int i=0;i<k;i++)
                        {
                            cout<<m+i<<" ";
                        }
                        cout<<endl;
                    }
                }
            }
            cout<<"请输入要拆分的数:"<<endl;
        }
    }

      但是程序中有k*m、(k-1)*k之类的运算,如果要求是64位数的运算的话,上面列举的成法肯定会溢出。所以要满足64位运算还要对上面的算法进行优化。

      首先,考虑拆分后的起始数m。应该不会有m>num/2,因为拆分两项时就有m+(m+1)=2*m+1了。所以有m<num/2。

      然后,考虑拆分项数k。有k<=num/m,也很简单,如果有k*m>num,还有那么多比m大的数呢,必然不满足拆分条件了。

      另外,考虑从1开始拆分项得到的项数k最多,(1+k)*k/2=num。为了不让后续计算溢出,求出k的最大取值。另num=0xffffffff,求上面二次方程有k<92681。于是得新的程序。

    #include <iostream>
    using namespace std;
    
    //64为数拆分版
    int main(int argc, char** argv)
    {
        unsigned long m;//拆分后起始的数
        unsigned long k;//分的项数
        unsigned long num;//要拆分的数
        cout<<"请输入要拆分的数:"<<endl;
        while(cin>>num)
        {
            for(m=1;m<num/2+1;m++)//拆分后的数中最小的不会比 要拆分的数 的一半还大
            {
                //拆分的项数不会大于 要拆分的数除以拆分后的数中的最小的那个
                //92681为(1+k)*k/2=0xffffffff的解,求此解释为了不让后续运算溢出
                for(k=2;k<=92681&&k<=num/m;k++)
                {
                    unsigned long tmp,tmp2;
                    tmp2 = k%2==0?k/2*(k-1):(k-1)/2*k;//有92681限制肯定不会溢出
                    if(0xffffffff-tmp2<k*m)//保证k*m+tmp2<0xffffffff,此处k*m+tmp2有可能溢出,所以用0xffffffff-tmp2与k*m比较
                    {
                        break;
                    }
                    //不用担心k*m溢出,因为k最大取num/m
                    tmp = k*m+tmp2;//上面逻辑保证了此处不会溢出
                    if(tmp>num)//后续计算没意义,直接跳出
                        break;
                    if(tmp==num)
                    {
                        cout<<"找到一个拆分组合:";
                        for(unsigned long i=0;i<k;i++)
                        {
                            cout<<m+i<<" ";
                        }
                        cout<<endl;
                        break;
                    }
                }
            }
            cout<<"请输入要拆分的数:"<<endl;
        }
    }

      上面的算法算是达到目的了,可是它实在是太慢了。输入最大的64位数4294967295要等上十几分钟才有结果,所以要对上面的算法继续优化。利用上面的程序观察输出规律,看看能否从中找出更好的算法。

      我发现具体有如下规律:

      从3开始每加2后得到的数会有一个2个数的拆分方法,如3+2=5可以拆分为2+3;3+2+2=7可以拆分为3+4。

      从6开始每加3后得到的数会有一个3个数的拆分方法。

      从10开始每加4后得到的数会有一个4个数的拆分方法。

      ……

      把规律列出如下:

      起始数          递增数  拆分格式

      3                 2         _+_

      6=3+3         3         _+_+_

      10=6+4       4         _+_+_+_

      15=10+5     5         _+_+_+_+_

      ……

      得到如上规律后,对之前的程序进行修改,依次判断输入的数是否满足拆分2、3、4、……个数的要求。如果满足再在此拆分的基础上找出具体的拆分数即可。程序如下:

    #include <iostream>
    using namespace std;
    
    //64为数拆分版
    int main(int argc, char** argv)
    {
        unsigned long i,j;
        unsigned long num;//要拆分的数
        unsigned long cnt=0;//可以有几组拆分
        cout<<"请输入要拆分的数:"<<endl;
        while(cin>>num)//输入要拆分的数
        {    
            cnt = 0;
            unsigned long tmp=1;
            for(i=2;i<=num/2+1&&i<92681;i++)//最大的数不会超过要拆分数的一半。92681为了防止溢出
            {
                tmp+=i;
                if(tmp>num)//这个要被减的数都比要拆分的数大了,后续没必要继续运算了
                {
                    break;
                }
                if((num-tmp)%i==0)//满足规律
                {
                    unsigned long begnum;
                    unsigned long sum=0;
                    begnum = num/i-i/2;
                    for(;sum!=num;begnum++)//找出拆分为i个数相加中最小的那个数
                    {
                        sum = 0;
                        for(j=0;j<i;j++)
                        {
                            sum+=begnum+j;
                        }
                    }
                    cout<<"找到一个拆分组合:";
                    for(j=0;j<i;j++)
                    {
                        cout<<begnum-1+j<<" ";//打印出来
                    }
                    cout<<endl;
                    cnt++;
                }
            }
            cout<<"找到拆分个数:"<<cnt<<endl;
            cout<<"请输入要拆分的数:"<<endl;
        }
    }

      经过这次优化后程序运行已经很快了。当然这个程序还能优化,比如那个92681,可以根据具体数值换成更小的数,不过要开根,这里就不做讨论了。

  • 相关阅读:
    使用SpringAOP获取一次请求流经方法的调用次数和调用耗时
    疫苗之殇与理性应对之道
    【做更好的职场人】理性、弹性、开放的沟通
    使用IntelljIDEA生成接口的类继承图及装饰器模式
    订单导出应对大流量订单导出时的设计问题
    预发和线上的自动化对比工具微框架
    从实战角度看如何构建高质量的软件:一线工程师的一份质量手记
    代码问题及对策
    若干设计经验教训小记
    输入输出无依赖型函数的GroovySpock单测模板的自动生成工具(上)
  • 原文地址:https://www.cnblogs.com/budapeng/p/3288800.html
Copyright © 2020-2023  润新知