#自我升华# 洛谷深基学习记录 第四章 循环程序结构设计

目录

目录

前言

知识积累

for语句和while语句

for循环语句

while循环语句

do-while循环语句

 多重循环嵌套

数字三角形

阶乘

数字出现次数

循环结构程序设计经典案例

省略循环体:级数求和(P1035)

省略循环条件:金币(P2669)

自加自减语句:数列求和(P5722)

浮点数误差与死循环:数列求和

质数、break语句和continue语句:质数口袋(P5723)

课后习题答案

习题4-1

习题4-2

习题4-3  数字反转(P1037)

习题4-4  斐波那契数列(P1720)

习题4-5  求极差(P5724)

习题4-6  最长连号(P1420)

习题4-7  质因数分解(P1075)

习题4-8  求三角形(P5725)

习题4-9  打分(P5726)

习题4-10  Davor(P4956)

习题4-11  津津的储蓄计划(P1089)


前言

       今天是10月24日,程序员节。想到未来的我大概率也是个程序员,那今天也差不多是我的节日了!在这样一个有意义的日子里,不学点什么都对不起未来的自己,于是我趁下午没有线下课,赶紧到图书馆复习一下循环语句,为接下来学数组函数这些打好基础,以下就是我对洛谷深基里的题目和代码的理解。

知识积累

for语句和while语句

for循环语句

       for循环语句的一般形式如下:

for ( 循环变量初始值;循环条件;每轮循环结束操作 ) {
        循环体

}

       实质上,for后面括号里的语句存在的意义就是规定循环次数,下面用示例分析这一功能。

例1. 多行输出苹果

       张三每吃一个苹果,都会在日记里写下自己今天到现在吃了几个苹果,为了巩固他的英语,他打算用英文来写,比如,他在吃完今天第五个苹果后,会写下“Today,I ate 5 apples.”。其它以此类推。要求写一个程序,当输入整数 n 时,输出 Today,I ate 5 apples. 。注意单复数变化。

#include
using namespace std;
int main(){
    int n;
    cin>>n;
    for(int i=0;i

       通过这道题,就可以理解for循环语句的意思了,循环语句括号里定义的变量 i 的用法之一就是计数,用来规定循环的次数,如果要循环 n 次,可以有 3 种写法,有时使用第三种会更方便:

for ( int  i = 0 ; i < n ; i + + ) {  } 

for ( int  i = 1 ; i < = n ; i + + ) {  }

for ( int  i = n ; i > 1 ; i - - ) {  }

例2. 最值打擂(P5718)

题目描述

给出 n 和 n 个整数​,求这 n 个整数中最小值是什么。

输入格式

第一行输入一个正整数 n,表示数字个数。

第二行输入 n 个非负整数,以空格隔开。

输出格式

输出一个非负整数,表示这 n 个非负整数中的最小值。

#include
using namespace std;
int main(){
	int n,num,Min;
	cin>>n;
	cin>>Min;//第二行输入的第一个数;
	for(int i=0;i>num;
		Min=min(num,Min);
//第二行输入的第一个数作为擂主(Min),
//接下来输入的数和他打擂,
//也就是比谁小,
//谁小谁就是新的擂主(Min);
	}
	cout<

从这道题中,我们学到的不仅是如何通过循环来判断最值,还有更重要的,一次多输入的实现,这道题要求输入第二行是n个数,一次性输入n个数,然后让循环语句来判断最值,for循环语句会重复执行循环体里的输入,实际上是读取,每次读取一个数,这是一个很重要的方法,通过在for循环体里使用 cin 来实现单次交互的多个输入,利用for循环语句每次循环依次读取的特点,实现重复判断后只有单次输出

例3. 分类平均(P5719)

题目描述

给定 n 和 k,将从 1 到 n 之间的所有正整数可以分为两类:A 类数可以被 k 整除(也就是说是 k 的倍数),而 B 类数不能。请输出这两类数的平均数,精确到小数点后 1 位,用空格隔开。

数据保证两类数的个数都不会是 0。

输入格式

输入两个正整数 n 与 k。

输出格式

输出一行,两个实数,分别表示 A 类数与 B 类数的平均数。精确到小数点后一位。

#include
//由于本题需要输出数字保留小数,
//所以用C语言风格可以减少代码量;
using namespace std;
int main(){
	int n,k;
	int Asum=0,Bsum=0;
//循环体里需要的变量,
//一定要先赋值,
//否则没有初值就会报错;
	scanf("%d %d",&n,&k);
	for(int i=k;i<=n;i+=k)
/*定义的变量i在这里就不是用来计数的了,
理解这个地方需要先分析一下题目,
题目要求的是在从1到n的整数里,
找到所有等于k的整数倍的数,
所以,循环没有必要从1开始,
可以直接从k开始,因为k是k的1倍,
此后每经过一次循环,加一个k,
这样所有的被赋值的i就都是k的倍数,
减少了很多没必要的循环,值得学习。*/
		Asum+=i;
	Bsum=(1+n)*n/2-Asum;
//这里是算从1到n里不是k的倍数的数之和,
//巧妙地应用了求和公式,值得学习;
	printf("%.1f ",double(Asum)/(n/k));
//这里的除数是 n/k ,
//其实就是这里所有k的倍数的数量,
//这样又避免了使用循环;
	printf("%.1f",double(Bsum)/(n-n/k));
//这两行输出体现了C语言的优越性,
//使用占位符 %.1f ,
//将浮点数保留一位小数再输出,
//比起C++中使用setprecision()函数要方便得多。
	return 0;	
}

       通过这道题,可以知道 for 语句括号里的变量 i 还可以有其他的使用方式,不一定要用来计数,可以通过赋给 i 题目要求输入的值,利用每轮结束操作来对 i 进行适当的处理,以期简化代码

       通过上面两题难度的对比,我们可知,for循环语句更适合明确知道重复次数的循环,这里的知道,指的是通过输入得知的循环次数,以及题目中暗含的循环次数。

while循环语句

while循环语句的一般形式如下:

while ( 循环成立条件 ){

        循环体

}

相较于for循环语句的形式,while循环语句变得更简单,少了变量 i 和循环结束处理,循环结束处理其实可以放在循环体中,无伤大雅。但是,while循环最大的优点,其实是它更适用于重复次数未知的循环,仅需要条件判断来进行循环。while语句的功能通过下例来说明。

例4. 一尺之棰(P5720)

题目描述

《庄子》中说到,“一尺之棰,日取其半,万世不竭”。第一天有一根长度为 a 的木棍,从第二天开始,每天都要将这根木棍锯掉一半(每次除 2,向下取整)。第几天的时候木棍的长度会变为 1?

输入格式

输入一个正整数 a,表示木棍长度。

输出格式

输出一个正整数,表示要第几天的时候木棍长度会变为 1。

#include
using namespace std;
int main(){
	int a,days=1;//因为第一天木棍不用锯,所以不参与循环;
	cin>>a;
	while(a>1)
/*当a比1大时就可以进入循环,
进行循环期间,
由于循环体里有对于a的操作,
所以a的值会改变,
第二次进入循环的a其实是被第一次循环处理过的a,
而非上面输入的a;
当某一次循环后,a不符合a>1的条件时,
就会跳出循环,执行循环下面的代码;*/
		days++,a/=2;
/*题目里说的向下取整,
其实就是省略每次除以2的余数,
直接对整数用 / 就可以了;
这里还用到了逗号表达式,简化了代码;*/
	cout<

       在这道题的代码里,运用了逗号表达式,逗号表达式就是用逗号将两个不同的表达式连在一起,变成了一条语句,这样就可以不需要使用花括号了,减少了代码量;

       另外,这道题还可以使用for循环语句来做,不过要稍微复杂一点,如下:

#include
using namespace std;
int main(){
	int a,n=1;
	cin>>a;
	for(int i=a;i>1;i/=2)
		n+=1;
	cout<

       事实上,while循环语句一直被当做for循环语句的简化版。还要注意的是,如果循环语句永远成立,就变成了死循环,运行不了,因此要特别注意执行循环的判断条件。

do-while循环语句

       do-while语句其实就是while语句的一种变体,while语句是先判断再执行循环体,而do-while语句则是先执行循环体再判断,判断条件成立就继续下一次循环,条件不成立就跳出循环,执行循环下面的语句。do-while循环语句的一般形式如下:

do {

        循环体

} while ( 循环成立条件 );

 和while语句的最大不同在于,do-while语句至少会执行一次循环体,而while语句的循环体可能一次也不会执行。下面是一个较为复杂的例子。

例5. 猜数游戏

       小洛机器人和你完猜数游戏!小洛随机选择并默默记下一个1~100的整数,你需要不断猜测这个数字是什么并输入验证。如果你输入的数字比小洛选择的数字小,小洛会输出“Too small!”;如果比小洛选择的数字大,小洛输出“Too large!”;如果刚好猜对,小洛输出“You are right!!”。如果一次没有猜中,就继续猜,直到猜中为止。

#include
#include
//包含产生随机数函数的头文件;
#include
//产生随机数需要配套的用于使随机数真正随机的头文件;
using namespace std;
int main(){
	int ans,guess;
	srand(time(0));
//使得多个随机数在连续产生时不相等,更加随机;
	ans=rand()%100+1;
/*rand()%n 就是一个随机数,
  从1到n-1的随机一个数,
  只要上面有一个srand,
  这个格式就可以多次使用;*/
	do{
		cin>>guess;
		if(guessans) cout<<"Too large!"<

       从上面代码可以得知,do-while语句最适合用于可持续输入判断类的题目,这种题目只有一个正确答案,输入正确答案后就会结束运行。

这段代码里的随机数生成也需要好好学习一下,主要需要记下以下代码:

#include

#include

{

        int a,b;

        srand(time(0));

        a=rand()%n;

        b=rand()%n;

}

 多重循环嵌套

数字三角形

例6. 数字直角三角形(P5721)

题目描述

给出 n,请输出一个直角边长度是 n 的数字直角三角形。所有数字都是 2 位组成的,如果没有 2 位则加上前导 0。

输入格式

输入一个正整数 n。

输出格式

输出如题目要求的数字直角三角形。

例如:n=5

输出:

0102030405
06070809
101112
1314
15
#include
using namespace std;
int main(){
	int cnt=0,n;//用cnt来记录输出到哪个数字了;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){//显而易见,要从第1行开始,输出n行;
		for(int j=1;j<=n+1-i;j++)
/*也可以看出要输出的数字和j有关,
这里是结合了三角形输出特点来写的,
比方说输入的n为5,就可以列出这样一个表:
    行数    这一行的数字个数
    1        5
    2        4
    3        3
    4        2
    5        1
而不同的行数就是外层循环定义的i(外层循环定义的数,在内层循环里一样能用),
每行的数字个数就是j,这里的i就是行数,j就是在这行输出的数的个数,
通过观察也直到每次循环的行数和数字个数之和恒等于6,也就是n+1,
通过总结i,j,n+1三者的数量关系,可以得出 j=n+1-i,
每次循环只要输出(n+1-i)个数即可;
这里还需注意的是,不可定义j=1,因为j代表的是一行输出数字的个数,而非只是用来计数的。*/
			printf("%02d",cnt++);
//这里用了占位符%0nd,n代表的是这个数所占的位数,若数字长度不足n,则在前面用0补齐;
		printf("\n");
	}
	return 0;
}

阶乘

例7. 阶乘之和(P1009)

题目描述

计算出 S=1!+2!+3!+⋯+n!(n<=20)。

输入格式:

一个正整数 n。

输出格式:

一个正整数 S,表示计算结果。

#include
using namespace std;
int main(){
	long long n,ans=0;
//由于题目要求的n的值为20,
//求阶乘之和一定非常大,
//所以用上最大的整数类型;
	cin>>n;
	for(int i=1;i<=n;i++){
//外层循环进行n次,
//就是n个阶乘相加;
		long long factor=1;
//在内外循环之间,
//定义一个变量来表示单个数的阶乘,
//这样内外循环都可以用;
		for(int j=1;j<=i;j++)
//内层循环进行i次;
			factor*=j;
//这样写比 factor=factor*i 更简洁,
//以后对一个数进行操作再更新,
//要按照这个格式来写;
		ans+=factor;
	}
	cout<

法2,在看课本写代码时发现了自己常犯的错误,于是写下解法二警醒自己:

#include
#include
using namespace std;
int main(){
	int n,sum=0;
	cin>>n;
	for(int i=1,ji=1;i<=n;i++){
		for(int j=1;j<=i;j++){
			ji*=j;
		}
		sum+=ji;
		ji=1;
	}
	cout<

数字出现次数

例8. 计数问题(P1980)

试计算在区间1到n(n<=10^6)的所有整数中,数字x(0<=x<=9)共出现了多少次?

#include
using namespace std;
int main(){
	int n,x,ans=0;
	cin>>n>>x;//输入区间最右端的数和想要统计个数的数字;
	for(int i=1;i<=n;i++){//进行n次循环,也就是依次读取从1到n这n个数字;
		int tmp=i,num;//用tmp来表示外层每次读取的数字;
		while(tmp!=0){//当tmp被赋值为0时才退出这个内层循环;
			num=tmp%10;//这就是内层读取的每个数位上的数;
			if(num==x)
				ans++;
			tmp/=10;
//完成个位数的读取识别后,整数除以10,就会将个位数删去,百位变成了个位,以此类推;
		}
	}
	cout<

循环结构程序设计经典案例

省略循环体:级数求和(P1035)

已知Sn=1+1/2+1/3+1/4+···+1/n 。现在给出一个整数k(1<=k<=15),要求计算出一个最小的n,使得Sn>k。

//没有循环体的示例 
#include
using namespace std;
int main(){
	int k,ans=0;
	cin>>k;
	for(double Sn=0;Sn<=k;ans++,Sn+=1.0/ans);
	/*当需要求的答案是题目中类似计数变量的n时,
可以转换思路,将循环变量设为和n相关的数列之和,
这在数列求和的题目中通用;
	这里省略了循环体,用每轮结束操作来代替,
这种方法在循环体为简单的数字增减时可以使用,
在每轮结束操作中用逗号表达式来缩短代码;*/ 
	cout<

省略循环条件:金币(P2669)

       国王将金币作为工资,发放给忠诚的骑士。第一天,骑士收到一枚金币;之后两天(第二天和第三天),每天收到两枚金币;之后三天(第四、五、六天),每天收到三枚金币;之后四天(第七、八、九、十天),每天收到四枚金币……;这种工资发放模式会一直这样延续下去:当连续 n 天每天收到 n 枚金币后,骑士会在之后的连续 n+1 天里,每天收到  n+1 枚金币。

请计算在前 k 天里,骑士一共获得了多少金币。

//没有循环条件的示例
#include
using namespace std;
int main(){
	int k,coin=0,day=0;
	cin>>k;
	for(int i=1;;i++)
		for(int j=1,j<=i;j++){//每轮大循环里,小循环i次; 
			coin+=i;day++;
			if(day==k){//终止循环的条件放在小循环里,就可以在某次小循环后退出循环; 
				cout<

       上面这种格式我将其命名为嵌套循环终止条件模板,可以在循环嵌套里使用,尤其是题目需要在某次小循环后就结束并输出,十分地适用!

自加自减语句:数列求和(P5722)

计算从1到n的和,不允许用等差数列求和公式。

//for循环简单做法 
#include
using namespace std;
int main(){
	int n,sum=0;
	cin>>n;
	for(int i=1;i<=n;i++) sum+=i;
	cout<
using namespace std;
int main(){
	int a=0,b=0,n;
	cin>>n;
	while(n--) 
/*这里是先判断n是否为0,不为0时,若是其他数字,则都认为是真(1),
就会顺着while语句n自减1;为0则跳出循环; */
		b+=++a;//可以看作a=a+1,b=b+a;这里用++a而不用a++,其中有门道。 
	cout<

       之前说过,i++ 和 ++i 的效果相同(i--和--i同理),都可以看成是 i=i+1,单独成句没有问题,但若与其他运算一起使用,则需要注意自加自减符号在不同位置的优先级差别。具体解释如下:

#include
using namespace std;
int main(){
	int a=0,b=0,A,B;
	A=a++;
	B=++b;
	cout<

浮点数误差与死循环:数列求和

计算0.1+0.2+0.3+···+(n-0.2)+(n-0.1)的值,其中n为不大于100的整数。

这题看起来和上面那道差不多,只是把整数累加改成小数累加了,所以有些人可能会像下面这样做:

#include
using namespace std;
int main(){
	int n,sum=0;
	cin>>n;
	for(double i=0.1;i!=n;i+=0.1){
		sum+=i;
		cout<

       但上面是错的!!错在循环条件判断,当i不等于n时就继续循环,就意味着当i等于n时结束循环,但是众所周知,整数和浮点数 由于计算机二进制储存浮点数存在误差 不能比较,只能用:

#include

fabs ( i - n ) < 1 e -6;

       用上面两句对错误代码进行适当地插入和替换,来判断i和n的差值是不是足够小,如果足够小,就近似看作二者相等。这样程序就可以正常运行而不会出现i和n一直不相等造成的死循环了。

       死循环就是输入数据后程序一直运行停不下来的状态,上面的错误代码运行后就进入了死循环,但我们看到的是一直没有输出,其实还可以用一种更直观的办法看到死循环,那就是在循环体的最后加上一句 cout << i << endl; 这样就可以看到每次加0.1的i在不断输出,甚至都超过了10,奔向了无穷大! 

       产生死循环的程序在OJ里是不能通过的,但是解决起来也很简单:检查条件变量是否正常变化,一般是再写一句输出条件变量来查看,然后检查判断条件是否可以成立,比如有没有浮点数误差。

       如果不想用这种数学函数的方法,还可以对条件里的大小判断稍加修改,将 i!=n改为i+0.01 ,切记不可以改成 i

       其实,遇到这种题目,最好首先把小数化成整数,到最后输出时再化回浮点数,这样就可以避免很多不必要的麻烦,具体做法如下:

#include
using namespace std;
int main(){
	int n,sum=0;
	cin>>n;
	for(int i=1;i<=10*n-1;i++){
/*因为题目里是类似0.1,0.2,0.3···的小数等差数列,
所以完全可以先算自然数求和,再将整数除以10,化为浮点数的答案;*/
		sum+=i;
	}
	cout<

质数、break语句和continue语句:质数口袋(P5723)

       小 A 有一个质数口袋,里面可以装各个质数。他从 2 开始,依次判断各个自然数是不是质数,如果是质数就会把这个数字装入口袋。口袋的负载量就是口袋里的所有数字之和。但是口袋的承重量有限,不能装得下总和超过 L 的质数。给出 L,请问口袋里能装下几个质数?将这些质数从小往大输出,然后输出最多能装下的质数个数,每行输出一个数。

科普 什么是质数?

  • 如果一个大于一的整数仅能被1和它本身整除,那么它就是一个质数,否则就是合数。
  • 1既不是质数也不是合数。
  • 如果整数n存在既不是1也不是n的因数,那么因数必定成对存在,其中一个不大于n的算术平方根,另一个不小于n的算术平方根,所以,只需知道从2到n的算术平方根之间存在一个那样的因数,那么n就不是质数。
#include
using namespace std;
int main(){
	int L,load=0,ans=0;
	cin>>L;
	for(int i=2;;i++){
	/*从2开始循环,是因为1既不是质数也不是合数; 
	这里条件判断是空的,说明有小循环;*/ 
		int is_prime=1;
		for(int j=2;j*j<=i;j++)
		/*还是从2开始,
		不过这里是到i的算术平方根结束,
		之所以不用sqrt函数(j<=sqrt(i)), 
		是因为像这样两边平方后只要算乘法了,
		计算机算乘法比开方快很多。*/ 
			if(i%j==0){
				is_prime=0;
				break;
			}/*这个if是和下面配合来用的,
			 意思是如果i能被j整除,
			 	就给is_prime赋值为0 ,
			 	这样下面的continue语句就执行不了了,
			 	就会判断是否超重,
			 		若超重就会退出循环,
			 		输出一共有几个质数;
					 若不超重就会输出这个质数i,
			 		然后给质数总数加一,
			 		给已有重量加上i。
			 如果i不能被j整除,
			 	那么is_prime的值还是1,
			 	下面就会执行continue语句,
			 	跳过循环体接下来的语句,
			 	给这个j加1,
			 	重新开始循环。*/ 
			  
		if(!is_prime) continue;
		if(i+load>L) break;
		cout<

一般来说,无判断条件的for循环会在循环体里加一些可以跳出循环的语句,若输出较简单,可以在合适的地方直接写 return 0; ,但设计循环的题目往往不会这么简单,因此会用到break语句和continue语句。

break语句:跳出一层循环。

continue语句:跳出本次循环中未执行的语句,重新开始一轮新的循环。

课后习题答案

习题4-1

#include
using namespace std;
int main(){
	int n,m,maxi=-100000000,num=0,xuhao=0;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>m;
		num++;
		if(m>maxi) maxi=m,xuhao=i;
	}
	cout<

习题4-2

#include
using namespace std;
int ans=0;
long double x,s=2,d=0;
int main()
{
    cin>>x;
    while(d

习题4-3  数字反转(P1037)

习题4-4  斐波那契数列(P1720)

习题4-5  求极差(P5724)

习题4-6  最长连号(P1420)

习题4-7  质因数分解(P1075)

习题4-8  求三角形(P5725)

习题4-9  打分(P5726)

习题4-10  Davor(P4956)

习题4-11  津津的储蓄计划(P1089)

你可能感兴趣的:(自我升华,1024程序员节,学习,c++,经验分享)