循环

重复 n 次

例如,老师罚小 A 做 20 个蹲起,他会一边做一边数(从 1 开始):“1,2,3,4,……。”

如何实现执行某种操作若干次呢?可以设定一个整数 \(i\),结合判断条件实现

  • \(i\) 赋一个初值 (初始为 \(1\)
  • 设定循环结束时 \(i\) 的终值或终止条件(\(i > 20\)
  • 每次循环\(i\) 的值如何变化(\(i\) 增加 \(1\)
  • 本质上就是产生一个序列,序列个数就是循环次数(1,2,3,……,20)

(1)重复 10000 次输出 a

第 1 部分:设定 i = 1;(仅在初始赋值时执行)

第 2 部分:当 i <= 10000; 时,继续执行循环;当 i > 10000 时,退出循环;

第 3 部分:每次循环++i;

C 语言中的 for 循环语法:

for (第 1 部分; 第 2 部分; 第 3 部分) { 代码 }

注意:小括号中有且只有 2 个分号!!!

#include <stdio.h>
//连续输出 10000 个 a
int main() {
	for (int i = 1; i <= 10000; ++i)
		printf("a");
	return 0;
}
#include <iostream>
int main() {
	for (int i = 1; i <= 10000; ++i)
		std::cout << 'a';
	return 0;
}

(2)重复 n 次输出 a,并要求每个 a 前面带上序号,逐行输出

#include <iostream>
//连续输出 n 个 a
int main() {
	int n;
    scanf("%d", &n);
	for (int i = 1; i <= n; ++i)
		printf("%d: a\n", i);
	return 0;
}
#include <iostream>
using namespace std;
int main() {
	int n;
    cin >> n;
	for (int i = 1; i <= n; ++i)
		cout << i << ": " << "a\n";
	return 0;
}

做数列的计算

(1)从 1 加到 10000 的和

第 1 部分:设定 i = 1;(只执行 1 次)

第 2 部分:当 i <= 10000 时,继续执行循环;

第 3 部分:++i;

练习:计算总分

//需要将 1 到 10000 的数字依次与 sum 相加
#include <iostream>
//连续输出 10000 个 a
int main() {
	int sum = 0;
	for (int i = 1; i <= 10000; ++i)
		sum += i;
	printf("%d", sum);
	return 0;
}

while 循环

for循环中的第1部分
while (for循环中的第2部分) {
	...
   for循环的第3部分
}

还是以求 1 到 10000 的数字之和为例:

#include <stdio.h>
int main() {
    int sum = 0, i = 1;
    while (i <= 10000) {
    	sum += i;
    	++i;
	}
	printf("%d", sum);
    return 0;
}

do-while 循环

for循环第1部分
do {
	...
   for 循环第3部分
} while (for循环的第2部分);

从代码可以看出,它第一次执行循环体语句前,并没有执行第2部分的判断,而是在第一次循环以后,从第二次循环起,才每次先判断再执行。

forwhile 循环最少可能执行 0 次,而 do-while 循环至少要执行 1 次。

#include <stdio.h>
int main() {
    int sum = 0, i = 1;
    do {
    	sum += i;
    	++i;
	} while (i <= 10000);
	printf("%d", sum);
    return 0;
}

break 语句

任何时候,在函数体内都可以使用 break; 直接退出循环(注意只能退出一层)。

continue 语句

使用 continue; 语句可以直接跳过当前循环体内位于 continue; 后面的所有语句,直接执行for循环的第3部分,进入下一次循环。

#include <stdio.h>
//输入n个数,求其中奇数的和
int main() {
    int n, i = 1, x, sum = 0;
    scanf("%d", &n);
    for (i = 1; i <= n; ++i) {
    	scanf("%d", &x);
    	//若x为偶数,则跳过后面的语句,直接去执行++i;输入下一个x
    	if (x%2 == 0) continue;
    	sum += x;
	}
	printf("%d", sum);
    return 0;
}

处理数列的前后关系

练习:连续上升数列

需要处理一个数列的每个数与其前后的数之间的关系时,通常我们需要用到一个辅助的临时变量,我们起名叫 last

这时我们可能会用一个特殊的输入方式:将第一个数和 n 在同一个 scanf 中先输入,并将其赋值给 last

break:跟在 switch 语句中遇到的一样,此时用 break 可以直接退出循环

循环外如何判断是 break 退出:i <= n;//与 for 循环中的条件一致

#include <iostream>
int main () {
	int i, n, x, last;
	scanf("%d%d", &n, &last);
	for (i = 2; i <= n; ++i) {
		scanf("%d", &x);
		//如果发现非递增,就退出循环,此时 i <= n
		if (x <= last) break;
		last = x;
	}
	if (i <= n) puts("NO");
	else puts("YES");
	return 0;
}
  • 三目运算符

#include <iostream>
int main () {
	int i, n, x, last;
	scanf("%d%d", &n, &last);
	for (i = 2; i <= n; ++i) {
		scanf("%d", &x);
		//如果发现非递增,就退出循环,此时 i <= n
		if (x <= last) break;
		last = x;
	}
	i <= n ? puts("NO") : puts("YES");
	return 0;
}

练习:病毒检测

#include <iostream>
int main() {
    int i, n, last = -1, x, cnt;
    scanf("%d", &n);
    for (i = 1; i <= n; ++i) {
        scanf("%d", &x);
        if (x%30 == 0) {
            if (x>last && last%30==0) ++cnt;
            else cnt = 1;
        }
        if (cnt == 5) break;
        last = x;
    }
    if (cnt == 5) printf("Yes\n%d", i-4);
    else puts("No");
    return 0;
}

上面代码的 for 循环中,其实 x%30 如果不为 0,后面的嵌套显得有些累赘,能否让代码更简洁呢?可以,需要引入循环的另一个关键字——continue

continue——停止当前这次循环,直接去执行 ++i跳入下一次循环

#include <iostream>
int main() {
   	int n, i, x, last = -1, cnt = 0;
   	scanf("%d", &n);
   	for (i = 1; i <= n; ++i, last = x) {
   		scanf("%d", &x);
   		if (x % 30) { cnt = 0; continue; }
   		cnt && x>last ? ++cnt : (cnt=1);
   		if (cnt == 5) break;
   	}
   	if (cnt < 5) puts("No");
   	else printf("Yes\n%d", i-4);
   	return 0;
}

循环输入问题

我们经常会遇到一些题目的输入是不给定输入数据个数的,这时候,可以使用以下两种方式来解决:

(1)使用 cin 语句

while (cin >> x)...

这种写法很简单,但是当输入数据达到 10 万左右时,输入时间过慢会导致超时。

(2)使用 scanf 语句

scanf 语句正常读入数据后,会返回本次读入的数据个数,例如:scanf("%d", &x); 成功读入一个整数 x 时,会返回 1scanf("%d%d", &n, &m); 成功读入 n,m 时会返回 2;而如果读入内容为空导致失败,则会返回 EOF

由于 OJ(Online Judge在线判题)网站使用测试数据文件自动判题,因此我们的代码实际上都是读入各个文件里的数据,当文件读完时,最后会读到文件结束符—— EOF,即 End Of File,这个符号对应整数 -1,所以,我们可以有如下 3 种写法:

第一种:

EOF 判断

while (scanf("%d", &x) != EOF) ...

第二种:

直接与 -1 判断

while (scanf("%d", &x) != -1) ...

第三种:

由于二进制 -1 的各个位均为 1,所以将其按位取反得到 0。

while (~scanf("%d", &x)) ...

循环语句相关的基础算法知识点

1、求和问题
2、最值问题
3、素数判断
4、数列(迭代法)
5、回文数
6、GCD与LCM
7、排列组合
8、数位分离
9、进制转换(秦九韶法)
10、二维图形
11、循环控制

最值问题

循环中的最值问题,需要初始化 max 以及 min 变量:

  • max 需要初始化为可能的最值,if (x > max) max = x;
  • min 需要初始化为可能的最值,if (x < min) min = x;

练习:“讨厌”的最值问题

#include <iostream>
int main () {
	int n, max = 0, min = 10000;
	scanf("%d", &n);
	// i + j + k == n, i > j > k
	for (int i = n-3; i > 0; --i)
		for (int j = i-1; j > 0; --j) {
			int k = n - i - j;
			if (k>0 && k<j) {
				if (i < min) min = i;
				if (k > max) max = k;
			}
		}
	max == 0 ? puts("-1 -1") : printf("%d %d", min, max);
	return 0;
}

数位运算

这类题目通常需要使用模运算,例如:取一个数的个位,就将这个数 %10 即可。

练习:数字滑滑梯2

#include <iostream>
int main() {
    int n, h = 1;
    scanf("%d", &n);
    for (int i = n/10; i; i /= 10) h *= 10;
    int m = n;
    do {
        n = n%10*h + n/10;
        printf("%d ", n);
    } while (n != m);
    return 0;
}

之所以使用 do{} while(); 的形式,是因为一开始 n!=m 就是不成立的,如果先判断 while(n != m),循环就会直接退出了。

while (for循环第2部分) {}

本题也可以对位数进行循环。

#include <iostream>
int main() {
    int n, h = 1, w = 1;
    scanf("%d", &n);
    for (int i = n/10; i; i /= 10) h *= 10, ++w;
    while (w--) {
        n = n%10*h + n/10;
        printf("%d ", n);
    }
    return 0;
}

打印图形

练习:三角形

#include <iostream>
int main() {
	int n;
	scanf("%d", &n);
	//两层for循环,外层对行进行循环,内层对列进行循环
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= i; ++j)
			putchar('*');//每次打印一个字符 put character
		puts("");//直接换行 put string
	}
	return 0;
}

素数

如何判断一个数是素数?——试除

如果一个大于 1 的正整数 n,从 2 开始,到 \(\sqrt{n}\) 结束,都不能整除 n,则 n 是素数。

证明:

假设 \(n=a\cdot b\),其中 \(a,b\) 为小于 \(n\) 的正整数,且 \(a<=b\)

则有 \(n=a\cdot b\ge a\cdot a=a^2\),即 \(a\le \sqrt{n}\)

因此,若 \(n\) 不是素数,它一定有一个小于等于 \(\sqrt{n}\) 的因子。

练习:判断素数

#include <iostream>
int main() {
    int n, i;
    scanf("%d", &n);
    for (i = 2; i*i <= n; ++i)
        if (n%i == 0) break;
    if (n<2 || i*i<=n) puts("NO");
    else puts("YES");
    return 0;
}

可以对代码进行优化,虽然变长了,但速度更快了。

#include <iostream>
int main() {
    int n;
    scanf("%d", &n);
    //小于2的正整数,或者大于 2 的偶数,都不是素数
    if (n<2 || n>2 && n%2==0) { puts("NO"); return 0; }
    for (int i = 3; i*i <= n; i += 2)
        if (n%i == 0) { puts("NO"); return 0; }
    puts("YES");
    return 0;
}

特殊数列

迭代法求数列元素

  • Fibonacci 数列

练习:Fibonacci 数列

#include <iostream>
//全局变量默认初值为 0
const int M = 10007;
int main() {
    //局部变量,默认初值不定
    int n, a, b, c;
    a = b = 1;
    scanf("%d", &n);
    //当 n 为 1或2 时,进行特判
    if (n==1 || n==2) { printf("1"); return 0; }
    for (int i = 3; i <= n; ++i)
        c = (a+b)%M, a = b, b = c;
    printf("%d", c);
    return 0;
}

有时候,我们完全可以通过赋初值来省略一些特判。

//利用变量 c 的初值可以做优化
#include <iostream>
const int M = 10007;
int main() {
    int n, a = 1, b = 1, c = 1;
    scanf("%d", &n);
    for (int i = 3; i <= n; ++i)
        c = (a + b) % M, a = b, b = c;
    printf("%d", c);
    return 0;
}

枚举

练习:回文数

2 种思路:

(1)从 1000 ~ 10000 逐个判断,循环 9000 次,执行 36000 次模运算,较为耗时

(2)当成 4 个数来看,\(ijji,1\le i \le 9, 0\le j \le 9\),执行 90 次,直接打出结果,效率高

#include <iostream>
int main(){
	for(int i = 1000;i < 10000; ++i)
		if(i/1000==i%10 && i/100%10==i/10%10)
			printf("%d\n", i);
	return 0;
}
#include <iostream>
int main () {
    for (int i = 1; i <= 9; ++i)
        for (int j = 0; j <= 9; ++j)
            printf("%d%d%d%d\n", i, j, j, i);
	return 0;
}

使用define宏定义for循环

#include <iostream>
#define loop(i, s, t) for(int i = s; i <= t; ++i)
int main () {
    loop(i, 1, 9) loop(j, 0, 9)
    	printf("%d%d%d%d\n", i, j, j, i);
	return 0;
}

最大公约数 \(GCD\) 和最小公倍数 \(LCM\)

辗转相除法(欧几里得定理)

定理1:\(gcd(a,b)=gcd(b,a\%b)\)

定理2:\(gcd(a,0)=a\)

\[\begin{aligned} 证明:&gcd(a,b) = gcd(b,a\%b)\\ 思路:&首先证明a,b的公约数集合A与b,a\%b的公约数集合B相等,然后可得 max\{A\}=max\{B\}\\ &(a,b) 表示 a 和 b 的公约数\\ 证:&令 d=(a,b),a=q_a\ast d,b=q_b\ast d,k=\lfloor{a/b}\rfloor\\ &a\%b = a-k\ast b=q_a\ast d-k\ast(q_b\ast d)=d\ast(q_a-k\ast q_b)\\ &可得 d|a\%b\\ &从而可得, a 和 b 的任意一个公约数 d,同时也是 b 和 a\%b 的公约数。\\ &同理可得,b 和 a\%b 的任意一个公约数 d,同时也是 a 和 b 的公约数。\\ &即 \{a,b\} 的公约数集合 A,与 \{b,a\%b\} 的公约数集合 B 相等,是同一个集合。\\ &得证。 \end{aligned} \]

例子:

\[\begin{aligned} gcd(36,60)&=gcd(60,36)\\ &=gcd(36,24)\\ &=gcd(24,12)\\ &=gcd(12,0)\\ &=12 \end{aligned} \]

练习:分苹果

#include <iostream>
int main () {
    int a, b, t, n, m;
    scanf("%d%d", &n, &m);
    a = n, b = m;
    while (b) t = a, a = b, b = t % b;
    printf("%d", n / a * m);
	return 0;
}

进制转换

  • 秦九韶多项式计算

\[\begin{aligned} &7x^5+5x^4+2x^3+6x+9\\ =&((((((0x +7)x)+5)x+2)x)x+6)x+9 \end{aligned} \]

// 令 ans 为不断迭代的中间结果,迭代完成后也即最终结果
// 输入时先输入x的值,再按顺序依次从5次方的系数开始输入:3 7 5 2 0 6 9
int ans = 0, i = 0, x;
scanf("%d", &x);
for (i = 5; i >= 0; --i) {
    scanf("%d", &e);
    ans = ans * x + e;
}
printf("%d", ans);

当遇到一个 x 进制数的时候,它的每个位就相当于上面多项式的每一项,将一个x进制转为十进制时,就是使用的这种算法

\[10110_2=1\times 2^4+0\times 2^3+1\times 2^2+1\times 2^1+0\times 2^0=22_{10} \]

练习:进制转换

#include <iostream>
typedef long long LL;
int main() {
    int n, r;
    LL ans = 0, b = 1;
    scanf("%d%d", &n, &r);
    while (n)
        ans += n % r * b, b *= 10, n /= r;
    printf("%lld", ans);
    return 0;
}

循环的嵌套

有时我们需要在一个循环里再执行另一个循环,例如:

\[\sum_{i=1}^n(3\times \sum_{j=1}^ij^2) \]

\(n=3\) 时,上式展开得

\[\begin{aligned} \sum_{i=1}^3(3\times \sum_{j=1}^ij^2)&=3\times(1^2)\\ &+3\times(1^2+2^2)\\ &+3\times(1^2+2^2+3^2) \end{aligned} \]

每一个 \(\sum\) 就是一层循环,我们需要在 \(\sum_{i=1}^n\) 的循环内,再加一个 \(\sum_{j=1}^i\) 的循环,写成如下形式:

s = 0;
for (i = 1; i <= n; ++i) {
	for (j = 1; j <= i; ++j) {
		s += j * j;
	}
}
printf("%d", s);
posted @ 2024-04-14 11:46  飞花阁  阅读(440)  评论(0)    收藏  举报
//雪花飘落效果