指数序列包括双指数与多指数,还可以引申出“指数积”,内容非常丰富;
本节探讨双指数序列并引申至3指数序列,同时探讨应用多种思路与算法求解双指数序列;
2-3指数序列
设x,y为非负整数,试计算集合:M={ 2^x,3^y | x>=0,y>=0 }的元素由小到大排列的双指数序列第n项与前n项之和;
1.递推设计要点:
集合由2的指数与3的指数组成,实际上是给出两个递推关系,集合元素所构成的序列为2指数序列;
设置变量f存储双指数升序序列的第k项,s存储前k项之和;
显然,第1项也是最小项为f=1(当x=y=0时),s=1;
从第2项开始,为了实现从小到大排序,设置a、b两个变量,相当于设置a、b两个队列:
- a是2为底的指数队列,b是3为底的指数队列,显然a!=b;
设置k循环(k=2,3,……,n,其中n为键盘输入整数),在k循环外赋初值:“a=2;b=3;”,在k循环中通过两个队列排头的比较选择赋值:
(1)、若a< b时,由赋值f=a确定为序列的第k项;然后a=a*2,即a按递推规律乘2,为后一轮比较做准备;
(2)、若a> b时,由赋值f=b确定为序列的第k项;然后b=b*3,即b按递推规律乘3,为后一轮比较做准备;
在这一递推过程中,变量a、b是变化的,分别代表2的指数与3的指数;
为标注第k项为指数的形式设置t:
t=2为2的指数,其指数为p2;
t=3为3的指数,其指数为p3;
2.双指数序列程序实现:
#include<stdio.h>
int main()
{
int k,n,t,p2,p3;
double a,b,s,f;
printf("求序列的第n项与前n项和,请输入n:");
scanf("%d",&n);
s=1;
a=2;
b=3;
p2=0;
p3=0;
for(k=2;k<=n;k++)
{
if(a<b)
{
f=a;
a=a*2;
t=2; /*t=2表示2的指数,p2为指数*/
p2++;
}
else
{
f=b;
b=b*3;
t=3; /*t=3表示3的指数,p3为指数*/
p3++;
}
s+=f;
}
printf("序列的第%d项为:%.0f ",n,f);
if(t==2) /*对第n项进行标注*/
printf("(2^%d) \n",p2);
else
printf("(3^%d) \n",p3);
printf("序列的前%d项之和为: %.0f \n",n,s);
}
3.程序运行示例及其注意事项:
求序列的第n项与前n项和,请输入n:50
序列的第50项为:1162261467 (3^19)
序列的前50项之和为: 3890875846
注意:这一递推设计在一重循环中完成,时间复杂度与空间复杂度均为O(n);
4.拓广为3指数序列:
设a、b、c为彼此互质的正整数,x、y、z为非负整数,集合M为:
- M={ a^x,b^y,c^z | x>=0,y>=0,z>=0 }
试计算M中有多少个元素处于指定区间[u,v]中,分别输出这些元素及其和,同时指出区间[u,v]中元素由小到大排列的第n项;
输入彼此互质的正整数a、b、c,同时输入区间下限与上限u、v以及指定项数n,输出处于区间[u,v]中的元素并统计其个数与和,同时输出区间中的元素升序列的第n项;
(1)、设计要点;
集合由指定底a、b、c的指数组成,实际上是给出3个递推关系,集合元素所构成的序列为3指数序列;
设置变量f存储3指数序列的各项,k统计区间[u,v]中的项数,s存储区间[u,v]中各项之和;
显然,第1项也是最小项为f=1(当x=y=z=0时),如果u=1时,项f=1在区间内,初值为:k=1,s=1;如果u>1时,项f=1不在区间内,初值为:k=0,s=0;
1)、循环设置;
设置永真循环,从第2项开始,为了实现从小到大排序,设置ax、bx、cx表征3个指数队列的3个变量:ax是底为a的指数队列,bx是底为b的指数队列,cx是底为c的指数队列,显然ax、bx与cx互不相等;
在循环外赋初值:ax=a;bx=b;cx=c;p1=p2=p3=0;
循环中通过各队列排头的比较选择赋值:
若ax< bx且ax< cx时,由赋值f=ax确定为序列的项f;然后ax=ax*a,即ax按递推规律乘a,体现ax为a的指数,为后一轮比较做准备;标志t=1,同时p1++;
若bx< ax且bx< cx时,由赋值f=bx确定为序列的项f;然后bx=bx*b,即bx按递推规律乘b,体现bx为b的指数,为后一轮比较做准备;标志t=2,同时p2++;
若cx< ax且cx< bx时,由赋值f=cx确定为序列的项f;然后cx=cx*c,即cx按递推规律乘c,体现cx为c的指数,为后一轮比较做准备;标志t=3,同时p3++;
2)、统计项数与求和;
如果所得项f满足f>=u,即项f进入指定区间[u,v],则用“k++;”统计区间中的项数,用“s+=f;”求和,同时输出f并通过t的值进行选择带指数标注,用赋值“e=f;”记录指定第n项的值(当k=n时);
3)、控制结束循环;
直至f>v时跳出循环,注意,判别if(f>v)放置在if(f>=u)的前面,使得k、s为指定区间[u,v]中的项数与和;
(2)、程序设计:
#include<stdio.h>
int main()
{
int a,b,c,k,n,t,p1,p2,p3;
double ax,bx,cx,f,e,s,u,v;
printf("请输入3指数的正整数底a、b、c(彼此互质):");
scanf("%d,%d,%d",&a,&b,&c);
printf("请指定区间[u,v]的u,v:");
scanf("%lf,%lf",&u,&v);
printf("请输入指定项n:");
scanf("%d",&n);
k=0;
s=0;
p1=p2=p3=0; /*各变量赋初值*/
printf("序列在区间[%.0f,%.0f]的元素为: \n",u,v);
if(u==1)
{
k=1;
s=1;
printf(" 1, ");
}
ax=a;
bx=b;
cx=c; /*ax,bx,cx赋初值*/
while(1)
{
if(ax<bx && ax<cx) /*a的指数赋值f,p1为指数*/
{
f=ax;
ax=ax*a;
t=1;
p1++;
}
else if(bx<ax && bx<cx) /*b的指数赋值f,p2为指数*/
{
f=bx;
bx=bx*b;
t=2;
p2++;
}
else /*c的指数赋值f,p3为指数*/
{
f=cx;
cx=cx*c;
t=3;
p3++;
}
if(f>v) /*超出v跳出循环*/
break;
if(f>=u)
{
k++;
s+=f; /*k统计个数并s求和*/
printf("%.0f",f); /*带指数标注显示区间中的项*/
if(t==1)
printf("(%ld^%ld), ",a,p1);
if(t==2)
printf("(%ld^%ld), ",b,p2);
if(t==3)
printf("(%ld^%ld), ",c,p3);
if(k%5==0)
printf("\n");
if(k==n) /*记录指定的第n项*/
e=f;
}
}
printf("\n共有%d个; \n",k);
printf("区间中元素之和为:%.0f; \n",s);
if(n<=k)
printf("区间元素升序第%d项为:%.0f \n",n,e);
else
printf("区间中元素不到%d项! \n",n);
}
(3)、程序运行示例及其注意事项:
请输入3指数的正整数底a、b、c(彼此互质):2,3,5
请指定区间[u,v]的u,v:2017,20170
请输入指定项n:5
序列在区间[2017,20170]的元素为:
2048(2^11), 2187(3^7), 3125(5^5), 4096(2^12), 6561(3^8),
8192(2^13), 15625(5^6), 16384(2^14), 19683(3^9),
共有9个;
区间中元素之和为:77901;
区间元素升序第5项为:6561
注意:统计与求和必须在指定的区间内实施,否则会出错,同时统计变量k与求和变量s的初值应考虑u=1 与 u>1两种情形;
指数积序列
在以上双指数序列基础上添增一个“积”字,即为双指数积序列;
设x、y为非负整数,试计算集合M={ 2^x · 3^y | x>=0,y>=0 }的元素不大于指定整数n的个数,并求这些元素从小到大排序的第m项;
指数积序列在双指数序列的基础上增加了一个“积”字,指数积序列的项既可以是2的指数、3的指数,也可以是这双指数之积;
加上“积”后,2*3=6、2^2*3=12、2*3^2=18 等都加入到序列中来,因此设计求解的难度加大了,也更具有吸引力;
下面在求解指数积序列的递增枚举设计的基础上给出一个带启发性的新颖搜索设计,最后给出一个递推设计;
1.递增枚举设计:
(1)、设计要点;
集合元素由2的指数与3的指数及其乘积组成。设元素从小到大排序的第k项为f(k),显然,f(1)=1,f(2)=2,f(3)=3;
设置a循环,a从3开始递增1至n,对每一个a(赋值给j)逐次用2试商,然后逐次用3试商;
试商后,若j>1,说明原a有2,3以外的因数,不属于该序列;若j=1,说明原a只有2,3的因数,属于该序列,把a赋值给序列第k项;
由于试商从小到大枚举,所得项无疑是从小到大的序列;
当a达到指定的整数n时,退出循环,输出指定项f(m);
(2)、递增枚举程序设计;
#include<stdio.h>
int main()
{
int k,m;
long a,j,n,f[1000];
printf("计算不大于n的项数,请指定n:");
scanf("%ld",&n);
printf("输出序列的第m项,请指定m:");
scanf("%d",&m);
f[1]=1;
f[2]=2;
k=2;
for(a=3;a<=n;a++)
{
j=a;
while(j%2==0) /*反复用2试商*/
j=j/2;
while(j%3==0)
j=j/3; /*反复用3试商*/
if(j==1) /*说明a只含2、3因数*/
{
k++;
f[k]=a; /*用a给f[k]赋值*/
}
}
printf("指数序列中不大于%ld的项数为: %d \n",n,k);
if(m<=k)
printf("从小到大排序的第%d项为: %ld \n",m,f[m]);
else
printf("所输的序号m大于序列的项数! \n");
}
(3)、程序运行示例及其注意事项:
计算不大于n的项数,请指定n:10000000
输出序列的第m项,请指定m:100
指数序列中不大于10000000的项数为: 190
从小到大排序的第100项为: 93312
2.带启发性的搜索设计:
上述枚举设计比较盲目,大量含有2、3以外因数的整数都一一检测,引发大量无效操作,为克服盲目性,避免大量的无效操作,下面给出一种有针对性、带启发性的搜索设计,可大大提高搜索效率;
(1)、设计要点;
1)、构建两个指数数组;
为实现有针对性枚举,设置2的指数与3的指数两个数组:
t2数组,存储2的指数:t2[0]为2^0,t2[1]为2^1,……,t2[p2-1]为2^(p2-1)<=n,t2[p2]>n;
t3数组,存储3的指数:t3[0]为3^0,t3[1]为3^1,……,t3[p3-1]为3^(p3-1)<=n,t3[p3]>n;
2)、构造指数积;
设置存储指数积的t数组,通过i,j二重枚举循环(i:0~p2-1;j:0~p3-1)构造指数积t=t2[i]*t3[i]:当i=0时,t即为3的指数;当j=0时,t即为2的指数;当i>0且j>0时,t为2与3的指数积;
若t>n,超过范围,不对f数组赋值:若t<=n,对f数组赋值:k++;f[k]=t;;
通过以上构造指数有针对性枚举,求出集合M中不大于指定整数n的所有k个元素;
3)、元素排序;
对这k个元素进行排序,以求得从小到大排序的第m项;
注意到集合M中不大于指定整数n的元素个数k的数量不会太大,采用较为简单的“逐项比较”排序法是可行的,实施排序中,从小到大排序到第m项即可,没有必要对所有k个元素排序;
(2)、程序设计;
#include<stdio.h>
int main()
{
int i,j,k,m,p2,p3;
double d,n,t,t2[100],t3[100],f[10000];
printf("请指定n,m:");
scanf("%lf,%d",&n,&m);
t=1;
p2=0; /*构造2的指数数组*/
while(t<=n)
{
t=t*2;
p2++;
t2[p2]=t;
}
t=1;
p3=0; /*构造3的指数数组*/
while(t<=n)
{
t=t*3;
p3++;
t3[p3]=t;
}
t2[0]=t3[0]=1;
k=0;
for(i=0;i<=p2-1;i++)
for(j=0;i<=p3-1;j++)
{
t=t2[i]*t3[j]; /*构造指数积t并检测赋值*/
if(t<=n)
{
k++;
f[k]=t;
}
}
printf("指数积序列中不大于%.0f的项数为:%d \n",n,k);
if(m<=k)
{
for(i=1;i<=m;i++) /*逐项比较排序至第m项*/
for(j=i+1;j<=k;j++)
if(f[i]>f[j])
{
d=f[i];
f[i]=f[j];
f[j]=d;
}
printf("从小到大排序的第%d项为:%.0f\n",m,f[m]);
}
else
printf("所输入的m大于序列的项数!\n");
}
(3)、程序运行示例及其注意事项:
请指定n,m:1000000000,300
指数积序列中不大于1的项数为:306
从小到大排序的第0项为:774840978
3.递推排序求解:
为了进一步提高搜索效率,试应用递推进行设计求解;
(1)、递推设计要点;
1)、的确递推关系;
为探索x+y=i时各项与x+y=i-1时各项之间的递推规律,剖析x+y的前几个值的情形:
x+y=0时,元素为1(初始条件);
x+y=1时,元素为2*1=2,3*1=3,共2项;
x+y=2时,元素有2*2=4,2*3=6,3*3=9,共3项;
x+y=3时,元素有2*4=8,2*6=12,2*9=18,3*3*3=27,共4项;
······
可以归纳出以下递推关系:x+y=i时,序列共i+1项,其中前i项是x+y=i-1时的所有i项分布乘2所得,最后一项为x+y=i-1时的最后一项乘3所得(即t=3^i);
注意,对x+y=i-1的所有i项分别乘2设为f[h]*2,分别检测是否小于n而大于0,同样,对t也必须检测是否小于n而大于0,只有小于n且大于0时才能赋值;
这里要指出,最后若干行可能不是完整的,即可能只有前若干项能递推新项,为此设置变量u:当一行有递推项时u=1,否则u=0,只有u=0时停止,否则会影响序列的项数;
2)、以n=1000为例具体说明递推的实施:
f(1)=1
i=1: f(2)=2 f(3)=3
i=2: f(4)=4 f(5)=6 f(6)=9
i=3: f(7)=8 f(8)=12 f(9)=18 f(10)=27
i=4: f(11)=16 f(12)=24 f(13)=36 f(14)=54 f(15)=81
i=5: f(16)=32 f(17)=48 f(18)=72 f(19)=108 f(20)=162 f(21)=243
i=6: f(22)=64 f(23)=96 f(24)=144 f(25)=216 f(26)=324 f(27)=486 f(28)=729
i=7: f(29)=128 f(30)=192 f(31)=288 f(32)=432 f(33)=648 f(34)=972
i=8: f(35)=256 f(36)=384 f(37)=576 f(38)=864
i=9: f(39)=512 f(40)=768
每一列的下一个数是上一个数的2倍,而每一行的最后一个数为3的指数;
3)、对所产生的项排序;
当所有递推项完成后,对所有k项应用逐项比较进行从小到大排序;
排序后输出指定的第m项;
(2)、递推排序程序实现;
#include<stdio.h>
int main()
{
int i,j,h,k,m,u,c[100];
double d,n,t,f[1000];
printf("计算小于n的项数,请指定n:");
scanf("%lf",&n);
printf("输出序列的第m项,请指定m:");
scanf("%d",&m);
k=1;
t=1.0;
i=1;
c[0]=1;
f[1]=1.0;
while(1)
{
u=0;
for(j=0;j<=i-1;j++)
{
h=c[i-1]+j;
if(f[h]*2<n && f[h]>0) /*第i行各项为前一行各项乘2*/
{
k++;
f[k]=f[h]*2;
u=1;
if(j==0) /*该行的第1项的项数值赋给c(i)*/
c[i]=k;
}
else
break;
}
t=t*3; /*最后一项为3的指数*/
if(t<=n && t>0)
{
k++;
f[k]=t; /*用t给f[k]赋值*/
}
if(u==0)
break;
i++;
}
for(i=1;i<k;i++) /*逐项比较排序*/
for(j=i+1;j<=k;j++)
if(f[i]>f[j])
{
d=f[i];
f[i]=f[j];
f[j]=d;
}
printf("指数序列中小于%f的项数为:%d\n",n,k);
if(m<=k)
printf("从小到大排序的第%d项为:%.0f\n",m,f[m]);
else
printf("所输入的m大于序列的项数!\n");
}
(3)、程序运行示例及其注意事项:
计算小于n的项数,请指定n:1000000000000
输出序列的第m项,请指定m:500
指数序列中小于1000000000000.000000的项数为:534
从小到大排序的第500项为:391378894848
比较以上3个算法设计,递增枚举设计简单,但时间复杂度高;
带启发性搜索设计的搜索速度明显快于递增枚举,且易于拓广至多指数积序列;
求指数积序列的递推设计的时间复杂度较低,但设计较难把握;
变通:请把2、3的指数一般化为a、b(从键盘输入的互质正整数)的指数;
如何改进带启发性的搜索设计,以进一步实现求解3指数积序列?