整数拆分是一种特殊的分解,要求把一个指定的整数(称为整数体或和数)分解为不重复的若干个整数(称为零数或部分)之和,不记零数的次序;
整数拆分的对象与要求多种多样,拆分设计的难度取决于对零数的限制条件;
本节具体探讨两种不同限制零数的拆分;
零数为连续整数
零数为连续正整数的整数拆分是最简单的一种拆分;
试把一个正整数n拆分为若干个(不少于2个)连续正整数之和,例如,n=15,有3种拆分:15=1+2+3+4+5,15=4+5+6,15=7+8;
对于给定的正整数n,求出并输出所有零数为连续正整数的整数拆分;
例如,对于n=15,输出以上3个解:对于n=15,无解(输出0);
下面试用两种不同的算法分别进行设计求解;
基本求和设计
1.说明:
定义变量s实施连续项求和,设计i(1~(n-1)/2)循环为连续项求和的起始项,j(i~(n+1)/2)循环作为连续求和的累加项;
在j循环中每加一项j后检测是否出现s>=n:
若未出现,所求连续项之和s不足n,则继续往后求和;
若出现s>=n,所求连续整数之和s已达到或超过n,即退出求和j循环,但在退出循环之前有必要进一步检测s=n是否成立,若有s=n,即找到一个解,应用变量c统计解的个数并输出一个序列;
这里指出,检测条件s>=n的确立避免了当“和已达到或超过n”情形下“继续求和”的无效操作;
2.程序设计:
#include<stdio.h>
#include<math.h>
int main()
{
long c,i,j,n,s;
printf("请输出拆分数n:");
scanf("%ld",&n);
c=0;
for(i=1;i<=(n-1)/2;i++) /*设置循环i枚举求和起始项*/
{
s=0;
for(j=i;j<=(n+1)/2;j++) /*设置循环j枚举求和累加项*/
{
s=s+j;
if(s>=n)
{
if(s==n)
{
c++;
printf("%d: %d+...+%d\n",c,i,j); /*统计并输出一个解*/
}
break;
}
}
}
printf("共有以上%d个解。\n",c); /*输出解的个数c*/
}
3.程序运行示例:
请输出拆分数n:2016
1: 1+...+63
2: 86+...+106
3: 220+...+228
4: 285+...+291
5: 671+...+673
共有以上5个解。
应用求和公式优化设计
应用连续正整数之和的公式可简化拆分设计;
1.说明:
设满足题意的连续正整数的个数为k,k的最大值为t,由求和公式:
- 1+2+……+t=(t*(t+1))/2=n
显然有t< sqrt(2*n);
设起始数为m的连续k项(2<=k< t)之和为给定整数n,由求和公式有:
- m+(m+1)+……+(m+k-1)=k*(2m+k-1)/2=n
解出m得:m=(2*n/k -k+1)/2;
建立关于连续正整数个数的k(2~t)循环,在循环中检验:
如果2n不能被k整除或2n/k -k+1不能被2整除,显然此时m非正整数,返回;
否则得正整数m=(2n/k -k+1)/2,即为所求拆分的一个解:m+……+(m+k+1);
2.程序设计:
#include<stdio.h>
#include<math.h>
int mian()
{
long c,k,m,n,t;
printf("请输入拆分数n:");
scanf("%ld",&n);
t=(long)sqrt(2*n);
c=0;
for(k=2;k<=t;k++)
{
if((2*n)%k>0 || (2*n/k+1-k)%2>0) /*检验m是否存在*/
continue;
m=(2*n/k+1-k)/2;
c++; /*统计并输出一个解*/
if(k==2)
printf("%d: %d+%d\n",c,m,m+1);
else
printf("%d: %d+%d\n",c,m,m+k-1);
}
printf("共有以上%d个解。\n",c); /*输出解的个数c*/
}
3.程序运行示例及其注意事项:
请输出拆分数n:2019
1: 334+...+339
2: 672+...+674
3: 1009+...+1010
共有以上3个解。
以上两个算法得到的解相同(解的顺序不同),但其复杂度相差较大:
- 注意到满足题意拆分的连续正整数的个数k的数量级为√n,基本求和算法设计的i循环平均频数估值为n,而内循环j循环平均数估值为√n,因而算法的时间复杂度为O(n^(3/2));应用求和公式优化设计的循环频数数量级为√n,因而其时间复杂度为O(√n);
显然应用求和公式设计的时间复杂度大大低于常规的基本求和算法设计;
当整数n很大时,应用常规的基本求和算法显然相当困难,而应用求和公式优化设计则快捷得很多;
零数取自指定集合
把指定整数s拆分为若干个零数之和,零数取自指定集合b,b集合由m个小于s的整数b1,b2,……,bm构成;
问共有多少种不同的拆分方法?展示出所有的这些拆分式;
1.说明:
注意到程序中a数组取值为1~m,此处b数组的下标也是1~m,因而设计求解一般的整数拆分问题递归程序,我们可以在以上程序的基础上,b数组的下标用a数组的值代替,求和过程中把程序中对a数组元素的求和t=t+a[j]改为对b数组元素求和t=t+b[a[j]],其中a[j]为b数组的下标;
2.程序设计:
#include<stdio.h>
#include<math.h>
#define MAXN 100
int a[MAXN],b[1000];
int t,n=0;
void comb(int m,int k,int s) /*建立递归函数comb(m,k,s)*/
{
int i,j;
for(i=m;i>=k;i--)
{
a[k]=i;
if(k>1)
comb(i-1,k-1,s);
else
{
for(t=0,j=a[0];j>0;j--)
t=t+b[a[j]];
if(t==s)
{
n++;
printf("%d=",s); /*满足条件时输出*/
for(j=a[0];j>1;j--)
printf("%2d+",b[a[j]]);
printf("%2d\n",b[a[1]]);
}
}
}
}
int main()
{
int m,ss,i,h,k,wmin,wmax;
printf("输入零数的个数:");
scanf("%d",&m);
printf("依次由小到大输入零数:\n");
for(i=1;i<=m;i++)
{
printf("b[%d]=",i);
scanf("%d",&b[i]);
}
printf("输入和数为:");
scanf("%d",&ss);
for(h=0,i=1;i<=m;i++)
{
h=h+b[i];
if(h>ss)
{
wmax=i-1;
break;
}
}
if(i>m) /*输入的零数组太小,程序返回*/
{
printf("输入的最大零数太小!\n");
}
for(h=0,i=1;i<=m;i++)
{
h=h+b[m-i+1];
if(h>ss)
{
wmin=i-1;
break;
}
}
for(k=wmin;k<=wmax;k++)
{
a[0]=k;
comb(m,k,ss);
}
printf("共以上%d个拆分式。\n",n);
}
3.程序运行示例及其注意事项:
输入零数的个数:6
依次由小到大输入零数:
b[1]=1
b[2]=2
b[3]=3
b[4]=5
b[5]=6
b[6]=9
输入和数为:15
15= 9+ 6
15= 9+ 5+ 1
15= 9+ 3+ 2+ 1
15= 6+ 5+ 3+ 1
共以上4个拆分式。
注意:以上输入m个零数时要求依次由小到大输入;