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

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

  分析:

  设输入的数为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,可以根据具体数值换成更小的数,不过要开根,这里就不做讨论了。

 

posted @ 2013-08-29 11:44  DKMP  阅读(762)  评论(0编辑  收藏  举报