C语言程序设计100例之(9):生理周期

时间:2022-07-08 02:27:53

例9    生理周期

问题描述

人生来就有三个生理周期,分别为体力、感情和智力周期,它们的周期长度为 23 天、28 天和33 天。每一个周期中有一天是高峰。在高峰这天,人会在相应的方面表现出色。例如,智力周期的高峰,人会思维敏捷,精力容易高度集中。因为三个周期的周长不同,所以通常三个周期的高峰不会落在同一天。对于每个人,我们想知道何时三个高峰落在同一天。

对于每个周期,我们会给出从当前年份的第一天开始,到出现高峰的天数(不一定是第一次高峰出现的时间)。你的任务是给定一个从当年第一天开始数的天数,输出从给定时间开始(不包括给定时间)下一次三个高峰落在同一天的时间(距给定时间的天数)。例如:给定时间为10,下次出现三个高峰同天的时间是12,则输出2(注意这里不是3)。

输入数据

输入四个整数:p, e, i 和d。 p, e, i 分别表示体力、情感和智力高峰出现的时间(时间从当年的第一天开始计算)。d 是给定的时间,可能小于p, e, 或 i。 所有给定时间是非负的并且小于365, 所求的时间小于等于21252。

输出要求

从给定时间起,下一次三个高峰同天的时间(距离给定时间的天数)。

输入样例

0 0 0 0

0 0 0 100

5 20 34 325

4 5 6 7

283 102 23 320

-1 -1 -1 -1

输出样例

Case 1: the next triple peak occurs in 21252 days.

Case 2: the next triple peak occurs in 21152 days.

Case 3: the next triple peak occurs in 19575 days.

Case 4: the next triple peak occurs in 16994 days.

Case 5: the next triple peak occurs in 8910 days.

(1)编程思路1。

假设从当年的第一天开始数,第k 天时三个高峰同时出现。符合问题要求的k必须大于d、小于等于21252(23×28×33),并满足下列三个条件:

1)(k-p) % 23 == 0

2)(k-e) % 28 == 0

3)(k-i) % 33 == 0

对区间[d+1,21252]中的每个k都进行三个条件的判断,若同时满足三个条件,则k就是所求。

(2)源程序1。

#include <stdio.h>

int main()

{

int p,e,i,d,caseNo = 0,k;

while(scanf("%d%d%d%d",&p,&e,&i,&d) &&p!=-1)

{

++caseNo;

for(k = d+1;(k-p)%23!=0 || (k-e)%28!=0|| (k-i)%33!=0; k++);

printf("Case %d: the next triple peak occurs in %d days.\n",caseNo,k-d);

}

return 0;

}

(3)编程思路2。

思路1中对区间[d+1,21252]中的每个k都进行三个条件的判断,开销很大,可以进行优化。

具体优化办法是:先从区间[d+1,21252]中找到第一个满足条件1)的体力高峰出现的时间k1,然后从k1、k1+23、k1+2*23、k1+3*23…这些时间中寻找第一个满足条件2)的情感高峰出现的时间k2,当然它也一定是体力高峰出现的时间;最后在k2、k2+23*28、k1+2*23*28、k1+3*23*28…这些时间中寻找第一个满足条件3)的时间k3。则k3-d就是所求的答案。

(4)源程序2。

#include <stdio.h>

int main()

{

int p,e,i,d,caseNo = 0,k;

while(scanf("%d%d%d%d",&p,&e,&i,&d) &&p!=-1)

{

++caseNo;

for(k = d+1;(k-p)%23;k++);     // 枚举体力高峰

while ((k-e)%28!=0)  k+=23;     // 枚举情感高峰

while ((k-i)%33!=0)  k+=23*28;  // 找到三高峰

printf("Case %d: the next triple peak occurs in %d days.\n",caseNo,k-d);

}

return 0;

}

习题9

9-1  硬币方案

问题描述

有50枚硬币,可能包括4种类型:1元、5角、1角和5分。

已知50枚硬币的总价值为20元,求各种硬币的数量。

例如:2、34、6、8就是一种方案。而2、33、15、0是另一个可能的方案,显然方案不唯一。

编写程序求出类似这样的不同的方案一共有多少种?

输入数据

输出要求

所有可能的方案,输出格式见输出样例。

输入样例

无输入

输出样例

1: 0 , 38 , 8 , 4

2: 1 , 36 , 7 , 6

3: 2 , 33 , 15 , 0

……

(1)编程思路。

直接对四种类型的硬币的个数进行穷举。其中,1元最多20枚、5角最多40枚、1角最多50枚、5分最多50枚。

另外,如果以元为单位,则5角、1角、5分会化成浮点型数据,容易计算出错。可以将1元、5角、1角、5分变成100分、50分、10分和5分,从而全部采用整型数据处理。

(2)源程序。

#include <stdio.h>

int main()

{

int a,b,c,d,cnt=0;

for(a=0;a<=20;a++)

for(b=0;b<=40;b++)

for(c=0;c<=50;c++)

for(d=0;d<=50;d++)

{

if(a*100+b*50+c*10+d*5==2000 && a+b+c+d==50)

{

printf("%d: %d , %d , %d , %d\n",++cnt,a,b,c,d);

}

}

return 0;

}

(3)穷举优化。

上面的程序采用穷举法求解,比较简单。但在穷举结构的设置、穷举参数的选取等方面存在着改进与优化的空间。

一般来说,在采用穷举法进行问题求解时,可从两个方面来优化考虑。

1)建立简洁的数学模型。

数学模型中变量的数量要尽量少,它们之间相互独立。这样问题解的搜索空间的维度就小。反应到程序代码中,循环嵌套的层次就少。例如,上面的程序中,采用变量a、b、c、d分别表示1元、5角、1角和5分硬币的枚数,对这4个变量穷举,循环层次为4层。实际上这4个变量彼此间有两个条件在约束,或者枚数等于50,或者总价值为20元。因此,可以只穷举3个变量,另外一个变量通过约束条件求出,从而将循环层次减少为3层。

2)减小搜索的空间。

利用已有的知识,缩小数学模型中各个变量的取值范围,避免不必要的计算。反应到程序代码中,循环体被执行的次数就减少。例如,在穷举时,先考虑1元的枚数a,最多为20枚(即0<=a<=20),再考虑5角的枚数b,若采用总价值不超过20元约束,则其枚数最多为(2000-a*100)/50枚(即0<=b<=(2000-a*100)/50),之后考虑1角的枚数c,其枚数最多为 (2000-a*100-b*50)/10(即0<=c<=(2000-a*100-b*50)/10)。这样穷举的循环次数会大大减少。

采用上述思路优化后的源程序如下。

#include <stdio.h>

int main()

{

int a,b,c,d,cnt=0;

for(a=0;a<=20;a++)

for(b=0;b<=(2000-a*100)/50;b++)

for(c=0;c<=(2000-a*100-b*50)/10;c++)

{

d=(2000-a*100-b*50-c*10)/5;    // 剩下的用5分硬币填充

if(a+b+c+d==50)

{

printf("%d: %d , %d , %d , %d\n",++cnt,a,b,c,d);

}

}

return 0;

}

也可以采用总枚数不超过50枚约束。先考虑1元的枚数a,最多为20枚(即0<=a<=20),再考虑5角的枚数b,则其枚数最多为(50-a)枚(即0<=b<=(50-a),之后考虑1角的枚数c,其枚数最多为 (50-a-b)枚(即0<=c<=50-a-b)。采用这种思路优化后的源程序如下。

#include <stdio.h>

int main()

{

int a,b,c,d,cnt=0;

for(a=0;a<=20;a++)

for(b=0;b<=50-a;b++)

for(c=0;c<=50-a-b;c++)

{

d=50-a-b-c;    // 剩下的用5分硬币填充

if(100*a+50*b+10*c+5*d==2000)

{

printf("%d: %d , %d , %d , %d\n",++cnt,a,b,c,d);

}

}

return 0;

}

9-2  和积三角形

问题描述

把和为正整数s的8个互不相等的正整数填入8数字三角形(如图1所示)中,若三角形三边上的数字之和相等且三边上的数字之积也相等,该三角形称为和积三角形。

C语言程序设计100例之(9):生理周期

图1  数字三角形

例如,和为45的和积三角形如图2所示。

C语言程序设计100例之(9):生理周期

图2  s=45的和积三角形

编写一个程序,输出和为s的和积三角形。

输入数据

一个正整数S(36≤S≤300)。

输出要求

所有和为S的和积三角形,要求输出的方案不重复。如图2中,8和9交换,或4和3交换,或同时交换9与4、8和3、2和12,所得到的3种方案均视为与图2给出的方案是同一种方案。

输入样例

45

输出样例

1:2 , 8 , 9 , 1 , 4 , 3 , 12 , 6 ,   s1=20, s2=144

说明

对照图2的数据,注意体会样例中8个数的输出顺序,另外是s1的值代表各边上整数的和,s2的值代表各边上整数的积。

(1)编程思路。

按输出样例的说明,设图1所示的数字三角形的8个数分布如下图3所示。

因为三角形的两个腰可以互相交换,为避免重复,不妨约定三角形中数字“下小上大、左小右大”,即 b1<b7、b2<b3且b6<b5。

C语言程序设计100例之(9):生理周期

图3  三角形分布示意图

这样,可以根据约定对b1、b7的值进行循环探索,设置:

b1的取值范围为1 ~ (s-21)/2;    (因除b1、b7外,其他6个数之和至少为21)

b7的取值范围为b1+1 ~ (s-28);  (因除b7外,其他7个数之和至少为28)

b4的取值范围为1 ~  (s-28);    (因除b4外,其他7个数之和至少为28)

同理,根据约定b2<b3,b6<b5,可设置:

b2 的取值范围为1 ~ (s-21)/2;   (因除b2、b3外,其他6个数之和至少为21)

b3 的取值范围为b2+1 ~  (s-28);

b6 的取值范围为1 ~  (s-21)/2;  (因除b5、b6外,其他6个数之和至少为21)

b5 的取值范围为b(6)+1 ~  (s-28);

b8 = s-(b1+b2+b3+b4+b5+b6+b7)

对所取的8个整数,需要进行以下4道检测:

1)若b8<=0,则不符合要求;

2)若这8个数出现相同数,则不符合要求;

3)若三边之和不等,则不符合要求;

4)若三边之积不等,则不符合要求。

若某8个数通过以上4道检测,即为一个解,打印输出,并统计解的个数。

由于需要对8个整数中是否出现相同数进行检测,因此可以将8个数保存在一个一维数组中,定义一维数组 int  b[9];其中数组元素b[1] ~ b[8]分别对应图3中的b1 ~ b8。

程序总体可以写成一个七重循环结构,如下:

for(b[1]=1;b[1]<=(s-21)/2;b[1]++)

for(b[7]=b[1]+1;b[7]<=s-28;b[7]++)

for(b[4]=1;b[4]<=s-28;b[4]++)

for(b[2]=1;b[2]<=(s-21)/2;b[2]++)

for(b[3]=b[2]+1;b[3]<=s-28;b[3]++)

for(b[6]=1;b[6]<=(s-21)/2;b[6]++)

for(b[5]=b[6]+1;b[5]<=s-28;b[5]++)

{

根据穷举的8个数,进行4道检测,确定是否为一组解;

}

4道检测中,除检查8个数中是否出现相同数复杂点外,其他均是简单计算并判断即可。

为检测8个数中是否出现相同的数,可以先设定一个标志 t=0;然后用循环依次将每个数与其后的每个数进行比较,若出现相同,则置t=1并退出循环。

循环执行结束后,若 t==1,则说明8个数中出现了相同的数;若 t保持初始设定值0,则说明8个数中不存在相同的数。算法描述为:

t=0;

for(i=1;i<=7;i++)

for(j=i+1;j<=8;j++)

if(b[i]==b[j])

{

t=1; i=7; break;

}

(2)源程序1。

#include <stdio.h>

int main()

{

int i,j,t,s,s1,s2,cnt,b[9];

scanf("%d",&s);

cnt=0;

for(b[1]=1;b[1]<=(s-21)/2;b[1]++)

for(b[7]=b[1]+1;b[7]<=s-28;b[7]++)

for(b[4]=1;b[4]<=s-28;b[4]++)

for(b[2]=1;b[2]<=(s-21)/2;b[2]++)

for(b[3]=b[2]+1;b[3]<=s-28;b[3]++)

for(b[6]=1;b[6]<=(s-21)/2;b[6]++)

for(b[5]=b[6]+1;b[5]<=s-28;b[5]++)

{

b[8]= s-(b[1]+b[2]+b[3]+b[4]+b[5]+b[6]+b[7]);

if(b[8]<=0)  continue;

t=0;

for(i=1;i<=7;i++)

for(j=i+1;j<=8;j++)

if(b[i]==b[j])

{  t=1; i=7; break; }

if(t==1)  continue;

s1= b[1]+b[2]+b[3]+b[4];

if(b[4]+b[5]+b[6]+b[7]!=s1 || b[1]+b[8]+b[7]!=s1)

continue;

s2=b[1]*b[2]*b[3]*b[4];

if(b[4]*b[5]*b[6]*b[7]!=s2 || b[1]*b[8]*b[7]!=s2)

continue;

cnt++;

printf("%d : ",cnt);

for(i=1; i<=8; i++)

printf("%d , ",b[i]);

printf("  s1=%d, s2=%d\n",s1,s2);

}

return 0;

}

(3)穷举优化思路。

上面的穷举程序设计虽然可行。但是,这个程序的运行速度太慢。例如将程序中的s=45改成s=89,即计算和为89的8个整数组成的和积三角形,程序运行后,可得到如下所示的结果。

1 : 6 , 14 , 18 , 1 , 9 , 8 , 21 , 12 ,   s1=39, s2=1512

2 : 8 , 12 , 15 , 1 , 16 , 9 , 10 , 18 ,   s1=36, s2=1440

3 : 8 , 4 , 27 , 2 , 12 , 3 , 24 , 9 ,   s1=41, s2=1728

4 : 15 , 9 , 16 , 1 , 12 , 10 , 18 , 8 ,   s1=41, s2=2160

程序得到以上4个解需等待较长时间。为了提高求解效率,必须对程序进行优化,可以从循环设置入手。具体思路为:

1)增加s+b1+b7+b4是否为3的倍数检测。

因为三角形三个顶点的元素在计算三边时各计算了两次,即s+b1+b7+b4=3*s1,则在b1、b4、b7循环中增加对s+b1+b7+b4是否能被3整除的检测。

若(s+b1+b7+b4)%3≠0,则直接continue,继续新的b1、b4、b7探索,而无需探索后面的b2、b3、b5和b6;

否则,记s1=(s+b1+b7+b4)/3,往下进行探索。

2)精简循环,把七重循环精简为五重。

保留根据约定对b1、b7和b4的值进行的循环探索,设置同前。优化对b2、b3、b5和b6的循环探索。可根据约定对b3、b5的值进行探索,设置:

b3的取值范围为(s1-b1-b4)/2+1 ~  s1-b1-b4;   注: s1=(s+b1+b7+b4)/3

b5的取值范围为(s1-b4-b7)/2+1 ~  s1-b4-b7;

同时根据各边之和为s1,计算出b2、b6和b8,即

b2=s1-b1-b4-b3

b6=s1-b4-b5-b7

b8=s1-b1-b7

这样,还同时精简了关于b8是否为正的检测,也精简了三边和是否相等的检测。只需检测b数组是否存在相同正整数与三边积是否相同即可。

(4)改进后的源程序。

#include <stdio.h>

int main()

{

int i,j,t,s,s1,s2,cnt,b[9];

scanf("%d",&s);

cnt=0;

for(b[1]=1;b[1]<=(s-21)/2;b[1]++)

for(b[7]=b[1]+1;b[7]<=s-28;b[7]++)

for(b[4]=1;b[4]<=s-28;b[4]++)

{

if((s+b[1]+b[4]+b[7])%3!=0)

continue;

s1=(s+b[1]+b[4]+b[7])/3;

for(b[3]=(s1-b[1]-b[4])/2+1;b[3]<s1-b[1]-b[4];b[3]++)

for(b[5]=(s1-b[4]-b[7])/2+1;b[5]<s1-b[4]-b[7];b[5]++)

{

b[2]=s1-b[1]-b[4]-b[3];

b[6]=s1-b[4]-b[7]-b[5];

b[8]=s1-b[1]-b[7];

t=0;

for (i=1; i<=7; i++)

for(j=i+1;j<=8;j++)

if(b[i]==b[j])

{ t=1;  i=7; break; }

if(t==1)  continue;

s2=b[1]*b[2]*b[3]*b[4];

if(b[4]*b[5]*b[6]*b[7]!=s2 || b[1]*b[8]*b[7]!=s2)

continue;

cnt++;

printf("%d : ",cnt);

for(i=1; i<=8; i++)

printf("%d , ",b[i]);

printf("  s1=%d, s2=%d\n",s1,s2);

}

}

return 0;

}

运行以上改进穷举的程序,当s=89时所得解与前相同,但时间大大缩短。

9-3  完美运算式

问题描述

把数字1、2、…、9这9个数字填入以下含加减乘除与乘方的综合运算式中的9个□中,使得该式成立

□^□+□□÷□□-□□×□=0

要求数字1,2,…、9这9个数字在式中都出现一次且只出现一次。

输入数据

输出要求

输出所有可能的填写方式,输出格式见输出样例。

输入样例

输出样例

1:3 ^ 5 + 87 / 29 - 41 * 6=0

……

(1)编程思路1。

设式中的6个整数从左至右分别为 a、b、x、y、z、c,其中x、y、z为2位整数,范围为12~98;a、b、c为一位整数,范围为1~9。

设置a、b、c、x、y、z循环,对穷举的每一组a、b、c、x、y、z,进行以下检测:

1)若x不是y的倍数,即 x % y!=0,则返回继续下一次穷举。

2)若等式不成立,即a^b+x/y-z*c!=0,则返回继续下一次穷举。

3)式中9个数字是否存在相同数字。将式中6个整数共9个数字进行分离,分别赋值给数组元素f[1]~f[9]。连同附加的f[0]=0(为保证9个数字均不为0),共10个数字在二重循环中逐个比较。

若存在相同数字,t=1,不是解,继续下一次穷举。

若不存在相同数字,即式中9个数字为1~9不重复,保持标记t=0, 是一组解,输出所得的完美运算式。并统计解的个数 n 。

(2)源程序1。

#include <stdio.h>

int main()

{

int a,b,c,x,y,z;

int i,j,k,t,n,f[10];

n=0;

for(a=1;a<=9;a++)

for(b=1;b<=9;b++)

for(c=1;c<=9;c++)

for(x=12;x<=98;x++)

for(y=12;y<=98;y++)

for(z=12;z<=98;z++)

{

if (x%y!=0) continue;

k=1;

for (i=1;i<=b;i++)     // 计算k=a^b

k=a*k;

if(k+x/y-z*c!=0) continue;

f[0]=0;

f[1]=a;f[2]=b;f[3]=c;   //  9数字个赋给f数组

f[4]=x/10; f[5]=x%10;

f[6]=y/10; f[7]=y%10;

f[8]=z/10; f[9]=z%10;

t=0;

for(i=0;i<=8;i++)

for(j=i+1;j<=9;j++)

if(f[i]==f[j])

{ t=1; break; }      //  检验数字是否有重复

if(t==0)

{

n++;             //  输出一个解,用n统计个数

printf("%d:%d ^ %d + %d / %d - %d * %d=0\n",n,a,b,x,y,z,c);

}

}

return 0;

}

(3)编程思路2。

对上面的程序进行优化。

由于要求的综合运算式为:a^b+x/y-z*c=0,那么,x=(z*c-a^b)*y。因此可设置a、b、c、y、z循环,对穷举的每一组a、b、c、y、z,计算x。这样处理,可省略x循环,同时省略x是否能被y整除,省略等式是否成立的检测。

计算x后,只要检测x是否为二位数即可。若计算所得x不是二位整数,则返回继续下一次穷举。

另外,式中9个数字是否存在相同数字可采用这样的方法:

定义f数组对6个整数分离出的9个数字的出现次数进行统计,即f[i]的值为式中数字i的个数,初值全赋值为0。统计后,若某一f[i](i=1~9)不为1,则一定不满足数字1、2、…、9这九个数字都出现一次且只出现一次,标记t=1,不是解,返回继续下一次穷举;若所有f[i]全为1,满足数字1、2、…、9这九个数字都出现一次且只出现一次,保持标记t=0,是解,输出所得的完美综合运算式。

(4)源程序2。

#include <stdio.h>

int main()

{

int a,b,c,x,y,z;

int i,k,t,n,f[10];

n=0;

for(a=1;a<=9;a++)

for(b=1;b<=9;b++)

for(c=1;c<=9;c++)

for(y=12;y<=98;y++)

for(z=12;z<=98;z++)

{

k=1;

for (i=1;i<=b;i++)

k=a*k;

x=(z*c-k)*y;

if(x<10 || x>98)  continue;

for(i=1;i<=9;i++)

f[i]=0;

f[a]++; f[b]++; f[c]++;   //  记录9个数字各自出现的次数

f[x/10]++;  f[x%10]++;   f[y/10]++;  f[y%10]++;

f[z/10]++;  f[z%10]++;

t=0;

for(i=1;i<=9;i++)

if(f[i]!=1)

{ t=1; break; }      //  检验数字是否有重复

if(t==0)

{

n++;

printf("%d:%d ^ %d + %d / %d - %d * %d=0\n",n,a,b,x,y,z,c);

}

}

return 0;

}