欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-05-09 15:02:52
例9 生理周期 问题描述 人生来就有三个生理周期,分别为体力、感情和智力周期,它们的周期长度为 23 天、28 天和33 天。每一个周期中有一天是高峰。在高峰这天,人会在相应的方面表现出色。例如,智力周期的高峰,人会思维敏捷,精力容易高度集中。因为三个周期的周长不同,所以通常三个周期的高峰不会落在同 ......

例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;