NOI C++ 课程体系 / 语法入门 / 第10讲
CSP-J 入门级 · 语法入门 · 综合复习与实战

C++ 综合复习与实战

循环 + 数组 + 嵌套 —— 三大核心知识融合,从基础遍历到算法思维,一站式攻克综合题型。

6 大教学模块 5 个精讲案例 3 种循环对比 3 道实践大题 5 大易错点 15 道分层练习
本讲 · 第 14/56 讲综合复习与实战
模块 01

情景导航

综合复习不是简单重复,而是把循环、数组、嵌套三大知识串联起来,形成解决复杂问题的完整能力。

故事故事引入 —— 小强的编程选拔赛

小强是校信息学竞赛队的成员。学校要选拔代表参加市级比赛,老师出了一套综合测试卷,其中最后一道题让小强犯了难:

"给定一个 n×n 的矩阵,请将其顺时针旋转90度后输出。"

小强会一维数组,会 for 循环,也会简单的二维数组遍历。但"旋转矩阵"需要他把循环、数组、嵌套全部结合起来,还要找到行列之间的对应关系。

他想了想,决定先把问题拆解开:

步骤用到的知识说明
第一步:读入矩阵二维数组 + for 循环用嵌套 for 循环读入 n×n 个整数
第二步:找规律数学观察观察旋转前后坐标的变化规律
第三步:输出新矩阵嵌套 for 循环 + 数组按新坐标关系输出

小强发现,旋转90度的规律是:原矩阵中第 i 行第 j 列的元素,旋转后变成第 j 行第 (n-1-i) 列。

他高兴地写出来了!这道题综合了 循环、数组、嵌套 三大知识点,正是我们今天要复习的核心内容。

①生活类比 —— 综合知识就像"做饭"

单独学一个知识点,就像学会了一样食材的用法。但做一道菜,需要把多种食材和技巧组合起来。综合题就是"做饭"——把循环、数组、嵌套组合起来解决实际问题。

🔄

for 循环

就像"按菜谱一步步做":洗菜→切菜→炒菜→出锅,每一步都知道做几次。

对应 for 循环
📦

数组

就像"储物柜":一排柜子,每个柜子有编号,可以存多个同类型物品。

对应数组
🪆

嵌套循环

就像"套娃":外层循环转一次,内层循环跟着转一圈,一圈又一圈。

对应嵌套循环
🎮

综合题

就像"游戏通关":需要综合运用所有技能(循环+数组+判断)才能打败最终BOSS。

对应综合实战
②综合知识体系总览

综合复习的核心是把三大知识模块串联起来,形成完整的解题能力。

知识模块核心语法典型操作综合题中的角色
for 循环for(初始化; 条件; 更新)遍历、累加、计数控制流程骨架
while 循环while(条件) { ... }条件未知次数处理不确定输入
do-whiledo { ... } while(条件);至少执行一次菜单/验证输入
一维数组int a[100];存取、遍历、排序数据存储容器
二维数组int a[10][10];行列访问、矩阵操作表格/矩阵数据
嵌套循环for + for矩阵遍历、图形打印处理多维数据
③核心口诀
综合复习口诀 循环数组是基础,嵌套组合破难题。
for 定次数 while 判,do-while 先做再判。
一维数组存线性,二维数组像表格。
行优先存是规则,i 行 j 列要记牢。
嵌套循环莫搞混,外层行来内层列。
综合题目拆步骤,先读后算再输出。
模块 02

知识探究

系统回顾循环、数组、嵌套三大知识模块的语法细节,通过案例由浅入深掌握综合应用。

🔁循环语法解剖

C++ 提供三种循环语句,各有适用场景。综合题中 for 循环 使用频率最高。

for ( 初始化 ; 条件判断 ; 更新表达式 ) { 循环体 }
① 初始化 — 只执行一次 ② 条件判断 — 每次循环前检查 ③ 更新表达式 — 每次循环后执行 ④ 循环体 — 重复执行的代码
for 循环
  • 次数已知时使用
  • 语法紧凑,变量作用域小
  • 适合遍历数组、计数
while 循环
  • 次数未知,条件驱动
  • 先判断后执行
  • 适合等待输入、文件读取
do-while 循环
  • 至少执行一次循环体
  • 先执行后判断
  • 适合菜单选择、输入验证
📦数组语法解剖

数组是存储多个同类型数据的容器,下标从 0 开始,访问时必须保证不越界。

int a [ 10 ] ;
类型 数组名 元素个数(定长)

一维数组初始化方式:

方式代码说明
全零初始化int a[5] = {0};所有元素为 0
部分初始化int a[5] = {1, 2};前两个赋值,其余为 0
自动推断大小int a[] = {1, 2, 3};大小为 3
全赋值int a[3] = {1, 2, 3};每个元素对应赋值

二维数组初始化:

C++二维数组声明与初始化
int a[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};
// a[0][0]=1, a[0][1]=2, ..., a[2][3]=12
// 行优先存储:先存第0行,再存第1行,最后第2行
🪆嵌套循环解剖

嵌套循环就是一个循环体内包含另一个循环。外层循环执行一次,内层循环执行完整一轮。

for(int i=0; i<n; i++) {
  for(int j=0; j<m; j++) {
    // 执行 n×m 次
  }
}
外层 i:控制行 内层 j:控制列 总执行次数 = n × m

🎬 嵌套循环执行过程演示(i=0,1,2; j=0,1,2):

1i=0, j=0 → 执行 a[0][0]
2i=0, j=1 → 执行 a[0][1]
3i=0, j=2 → 执行 a[0][2]
4i=1, j=0 → 执行 a[1][0]
5i=1, j=1 → 执行 a[1][1]
6i=1, j=2 → 执行 a[1][2]
7i=2, j=0 → 执行 a[2][0]
8i=2, j=1 → 执行 a[2][1]
9i=2, j=2 → 执行 a[2][2]
⚡三种循环对比辨析
对比项forwhiledo-while
执行次数已知未知至少1次
判断时机循环前循环前循环后
变量作用域仅在循环内循环外也可用循环外也可用
典型场景遍历数组、计数条件等待菜单/验证
可互相替代是(灵活)是(需手动更新)有限(需至少一次)
案例1九九乘法表(嵌套 for 循环经典)

题目:输出 9×9 乘法表,格式如下:

运行结果
1*1=1
1*2=2  2*2=4
1*3=3  2*3=6  3*3=9
...
1*9=9  2*9=18  ...  9*9=81
C++九九乘法表
#include <iostream>
using namespace std;
int main() {
    for (int i = 1; i <= 9; i++) {
        for (int j = 1; j <= i; j++) {
            cout << j << "*" << i << "=" << j*i << "  ";
        }
        cout << endl;
    }
    return 0;
}

逐行解析:

  • 外层 i 从 1 到 9,控制当前行号
  • 内层 j 从 1 到 i,控制当前行打印几个乘法式
  • j <= i 保证输出三角形形状(不是矩形)
  • 每行结束后 cout << endl 换行
案例2数组逆序输出

题目:读入 n 个整数,逆序输出。

C++数组逆序
#include <iostream>
using namespace std;
int main() {
    int n;
    cin >> n;
    int a[100];
    for (int i = 0; i < n; i++) {
        cin >> a[i];  // 读入数组
    }
    for (int i = n - 1; i >= 0; i--) {
        cout << a[i] << " ";  // 逆序输出
    }
    // 结果:原数组 a[0]...a[n-1] → 输出 a[n-1]...a[0]
    return 0;
}

关键技巧:正序读入(i 从 0 到 n-1),逆序输出(i 从 n-1 到 0)。这是数组操作中最基础的模板。

案例3冒泡排序(嵌套循环 + 数组)

题目:读入 n 个整数,用冒泡排序从小到大排序后输出。

C++冒泡排序
#include <iostream>
using namespace std;
int main() {
    int n;
    cin >> n;
    int a[100];
    for (int i = 0; i < n; i++) cin >> a[i];

// 冒泡排序:外层控制轮数,内层控制每轮比较
    for (int i = 0; i < n - 1; i++) {       // 共 n-1 轮
        for (int j = 0; j < n - 1 - i; j++) { // 每轮比较 n-1-i 次
            if (a[j] > a[j+1]) {
                int t = a[j]; a[j] = a[j+1]; a[j+1] = t; // 交换
            }
        }
    }

for (int i = 0; i < n; i++) cout << a[i] << " ";
    return 0;
}

复杂度分析:外层循环 n-1 轮,内层循环逐轮减少,总比较次数 = (n-1) + (n-2) + ... + 1 = n×(n-1)/2,时间复杂度 O(n²)。

案例4杨辉三角(二维数组 + 嵌套循环)

题目:输出 n 行杨辉三角。杨辉三角特点:每行首尾为1,中间每个数 = 上方两数之和。

C++杨辉三角
#include <iostream>
using namespace std;
int main() {
    int n;
    cin >> n;
    int a[100][100] = {0};

// 填充杨辉三角
    for (int i = 0; i < n; i++) {
        a[i][0] = 1;              // 每行第一个为1
        a[i][i] = 1;              // 每行最后一个为1
        for (int j = 1; j < i; j++) {
            a[i][j] = a[i-1][j-1] + a[i-1][j]; // 上方两数之和
        }
    }

// 输出
    for (int i = 0; i < n; i++) {
        for (int j = 0; j <= i; j++) {
            cout << a[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}

核心规律:a[i][j] = a[i-1][j-1] + a[i-1][j],即当前位置 = 正上方 + 左上方。这是二维数组递推的经典应用。

案例5螺旋矩阵(嵌套循环 + 边界控制)

题目:输入 n,生成一个 n×n 的螺旋矩阵(从左上角开始,按顺时针方向填入 1 到 n²)。

例如 n=4 时:

运行结果 (n=4)
 1  2  3  4
12 13 14  5
11 16 15  6
10  9  8  7
C++螺旋矩阵
#include <iostream>
using namespace std;
int main() {
    int n;
    cin >> n;
    int a[100][100] = {0};
    int top = 0, bottom = n-1;
    int left = 0, right = n-1;
    int num = 1;

while (num <= n*n) {
        // 从左到右填上边
        for (int j = left; j <= right; j++) a[top][j] = num++;
        top++;
        // 从上到下填右边
        for (int i = top; i <= bottom; i++) a[i][right] = num++;
        right--;
        // 从右到左填下边
        for (int j = right; j >= left; j--) a[bottom][j] = num++;
        bottom--;
        // 从下到上填左边
        for (int i = bottom; i >= top; i--) a[i][left] = num++;
        left++;
    }

// 输出矩阵
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++)
            cout << a[i][j] << " ";
        cout << endl;
    }
    return 0;
}

解题思路:用四个边界变量 (top, bottom, left, right) 控制填充范围,每填完一条边就收缩对应边界。这是综合循环+数组+边界控制的经典难题。

模块 03

实践应用

通过完整的综合题目,练习循环、数组、嵌套的综合运用。每道题包含题目、输入输出样例、多种解法和思考。

📝题目1:矩阵转置

题目描述:给定一个 n×m 的矩阵 A,请输出它的转置矩阵 B。矩阵转置即行列互换:B[j][i] = A[i][j]。

输入样例:

输入
3 4
1 2 3 4
5 6 7 8
9 10 11 12

输出样例:

输出
1 5 9
2 6 10
3 7 11
4 8 12

题目分析:转置就是把原矩阵的第 i 行第 j 列变成新矩阵的第 j 行第 i 列。需要两个嵌套 for 循环:一个读入,一个按转置关系输出。

方法一直接转置存储
C++方法一:先转置存储再输出
#include <iostream>
using namespace std;
int main() {
    int n, m;
    cin >> n >> m;
    int a[100][100], b[100][100];
    // 读入矩阵
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            cin >> a[i][j];
    // 转置:b[j][i] = a[i][j]
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            b[j][i] = a[i][j];
    // 输出转置矩阵 (m行n列)
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++)
            cout << b[i][j] << " ";
        cout << endl;
    }
    return 0;
}
方法二直接按转置关系输出
C++方法二:不额外存储,直接输出
#include <iostream>
using namespace std;
int main() {
    int n, m;
    cin >> n >> m;
    int a[100][100];
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            cin >> a[i][j];
    // 输出时:第i行第j列输出 a[j][i]
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++)
            cout << a[j][i] << " ";
        cout << endl;
    }
    return 0;
}

说明:方法二更节省空间,不需要额外开辟 b 数组。核心是理解转置后输出顺序:外层循环控制列(新矩阵的行),内层循环控制行(新矩阵的列)。

引入思考:如果要求原地转置(不额外开数组),且 n=m(方阵),你能做到吗?提示:沿主对角线交换 a[i][j] 和 a[j][i]。

📝题目2:数对之和(洛谷 P1046 改编)

题目描述:给定 n 个整数和一个目标值 k,统计数组中有多少对不同的数之和等于 k。注意 (i,j) 和 (j,i) 算同一对。

输入样例:

输入
6 10
1 7 9 2 3 5

输出样例:

输出
2

题目分析:需要两层循环:外层选第一个数,内层选第二个数,检查两数之和是否等于 k。注意避免重复计数。

C++数对之和
#include <iostream>
using namespace std;
int main() {
    int n, k;
    cin >> n >> k;
    int a[1000];
    for (int i = 0; i < n; i++) cin >> a[i];

int count = 0;
    // 双重循环枚举所有数对
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) { // j从i+1开始避免重复
            if (a[i] + a[j] == k) {
                count++;
            }
        }
    }
    cout << count << endl;
    return 0;
}

复杂度:O(n²),对于 n≤1000 完全可行。如果 n 更大,可以用哈希表优化到 O(n)。

引入思考:如果要求输出所有满足条件的数对,而不仅仅是计数,代码该怎么改?如果数组已排序,能否用"双指针"方法优化到 O(n)?

📝题目3:图像旋转90度(综合大题)

题目描述:给定一个 n×n 的 01 矩阵表示一幅图像(0 表示白,1 表示黑),请将图像顺时针旋转 90 度后输出。

输入样例:

输入
3
1 0 1
0 1 0
1 0 1

输出样例:

输出
1 0 1
1 1 1
1 0 1

题目分析:顺时针旋转90度的坐标变换规律:原矩阵 a[i][j] → 新矩阵 b[j][n-1-i]。也可以理解为:先转置,再每行左右翻转。

C++图像旋转90度
#include <iostream>
using namespace std;
int main() {
    int n;
    cin >> n;
    int a[100][100], b[100][100];
    // 读入矩阵
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            cin >> a[i][j];

// 顺时针旋转90度:b[j][n-1-i] = a[i][j]
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            b[j][n-1-i] = a[i][j];

// 输出旋转后的矩阵
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++)
            cout << b[i][j] << " ";
        cout << endl;
    }
    return 0;
}

引入思考:如果不使用额外数组 b,能否在原数组上完成旋转?提示:先沿主对角线转置,再左右翻转每一行。这种方法叫"原地旋转",空间复杂度 O(1)。

模块 04

总结提升

回顾本课核心要点,掌握万能模板,并通过拓展案例深化理解。

📌核心要点总结
知识核心要点万能模板
for 循环for(初始化; 条件; 更新)for(int i=0; i<n; i++) { ... }
while先判后执,次数未知while(cin>>x && x!=0) { ... }
do-while先执后判,至少一次do { ... } while(条件);
一维数组下标从0开始,大小固定int a[100]; for(int i=0;i<n;i++) cin>>a[i];
二维数组行优先存储,a[i][j]for(i=0;i<n;i++) for(j=0;j<m;j++) cin>>a[i][j];
嵌套循环外层控制行,内层控制列总次数 = 外层次数 × 内层次数
冒泡排序每轮把最大/最小"冒泡"到末尾外层n-1轮,内层n-1-i次比较
矩阵操作行列变换是核心转置:a[j][i] ↔ a[i][j]
🚀拓展案例(一题多解)
拓展1数组逆序:三种解法

题目:读入 n 个整数,逆序输出。

解法一:倒序遍历输出

C++解法一:倒序遍历
for (int i = n-1; i >= 0; i--) cout << a[i] << " ";

解法二:原地交换

C++解法二:原地交换
for (int i = 0; i < n/2; i++) {
    int t = a[i]; a[i] = a[n-1-i]; a[n-1-i] = t;
}
for (int i = 0; i < n; i++) cout << a[i] << " ";

解法三:用新数组

C++解法三:新数组
int b[100];
for (int i = 0; i < n; i++) b[n-1-i] = a[i];
for (int i = 0; i < n; i++) cout << b[i] << " ";
拓展2字符统计:两种方案

题目:读入一行字符串,统计每个字母出现的次数。

方案一:用数组统计

C++方案一:数组统计
int cnt[26] = {0};
char c;
while (cin.get(c) && c != '
') {
    if (c >= 'a' && c <= 'z') cnt[c-'a']++;
    if (c >= 'A' && c <= 'Z') cnt[c-'A']++;
}
for (int i = 0; i < 26; i++)
    if (cnt[i] > 0) cout << (char)(i+'a') << ":" << cnt[i] << endl;
拓展3矩阵行求和

题目:给定 n×m 矩阵,求每行的和。

C++矩阵行求和
for (int i = 0; i < n; i++) {
    int sum = 0;
    for (int j = 0; j < m; j++)
        sum += a[i][j];
    cout << "第" << i+1 << "行和: " << sum << endl;
}
💭思考与启发

对比上面的解法,你有什么启发?

1. 数组逆序一题三解:倒序遍历最简单,原地交换最省空间,新数组最直观。实际编程中要根据需求选择。

2. 嵌套循环的本质:外层控制"行",内层控制"列"。矩阵问题的核心就是找到行列之间的坐标变换关系。

3. 这个代码适合解决什么问题?:循环+数组的组合可以解决几乎所有"批量数据处理"问题——排序、查找、统计、矩阵运算等。

4. 能否设计新算法?:比如数对之和问题,O(n²) 双重循环可以优化为 O(n) 哈希表。这就是算法思维的提升。

模块 05

学习检测

检测你对循环、数组、嵌套的掌握程度。每个案例包含错误代码、报错原因、正确写法和预防口诀。

⚠️5大易错点
易错点 1

下标越界

错误写法
int a[10];
for (int i = 0; i <= 10; i++) {
    cin >> a[i]; // i=10 时越界!
}
正确写法
int a[10];
for (int i = 0; i < 10; i++) {
    cin >> a[i]; // i 最大为 9,安全
}
预防口诀:数组大小 n,循环条件用 < n,不要用 <= n!
易错点 2

数组未初始化

错误写法
int a[100];
int sum = 0;
for (int i = 0; i < 100; i++)
    sum += a[i]; // a[i] 是随机值!
正确写法
int a[100] = {0}; // 全部初始化为0
int sum = 0;
for (int i = 0; i < 100; i++)
    sum += a[i];
预防口诀:数组使用前先初始化,{0} 全零最省心。
易错点 3

嵌套循环搞反行列

错误写法
// 想输出矩阵,但行列搞反了
for (int j = 0; j < m; j++) // 外层用了列
    for (int i = 0; i < n; i++) // 内层用了行
        cout << a[i][j] << " ";
正确写法
// 外层行i,内层列j
for (int i = 0; i < n; i++) // 外层控制行
    for (int j = 0; j < m; j++) // 内层控制列
        cout << a[i][j] << " ";
预防口诀:输出矩阵先想"先走哪"——先走行就外层 i,先走列就外层 j。
易错点 4

char 数组忘 \0

错误写法
char s[5];
cin >> s; // 输入 "hello" 需要6个字节
// 但没有空间存 \0,会越界!
正确写法
char s[6]; // 5个字符 + 1个\0
cin >> s;
预防口诀:char 数组开大小要 +1,给 \0 留位置。
易错点 5

for 循环分号陷阱

错误写法
for (int i = 0; i < 10; i++); // 多了个分号!
{
    cout << i << endl; // i 未定义!
}
正确写法
for (int i = 0; i < 10; i++) {
    cout << i << endl;
}
预防口诀:for 后面不要加分号,大括号里才是循环体。
📋考前速查表
操作代码模板
读入一维数组for(int i=0;i<n;i++) cin>>a[i];
遍历一维数组for(int i=0;i<n;i++) cout<<a[i]<<" ";
读入二维数组for(i=0;i<n;i++) for(j=0;j<m;j++) cin>>a[i][j];
遍历二维数组for(i=0;i<n;i++) for(j=0;j<m;j++) cout<<a[i][j]<<" ";
数组求和for(int i=0;i<n;i++) sum+=a[i];
找最值sort(a,a+n); 或遍历比较
冒泡排序外层n-1轮,内层n-1-i次比较交换
矩阵转置b[j][i] = a[i][j]
杨辉三角a[i][j] = a[i-1][j-1] + a[i-1][j]
数组逆序for(i=0;i<n/2;i++) swap(a[i],a[n-1-i]);
📌知识点速记卡
循环三要素

初始化 → 条件判断 → 更新表达式。缺一不可,否则要么不执行,要么死循环。

数组下标

从 0 开始,到 n-1 结束。访问 a[n] 就是越界,会读到垃圾值或程序崩溃。

嵌套循环次数

外层执行 n 次,内层每次执行 m 次,总共 n×m 次。打印矩阵就是典型应用。

二维数组存储

行优先:先存第0行所有列,再存第1行所有列……a[i][j] 中 i 是行号,j 是列号。

break 与 continue

break 跳出整个循环,continue 跳过本次进入下一次。综合题中常用于提前终止。

边界条件

循环条件用 < 还是 <=?数组大小是否够?这些是调试时最先检查的地方。

模块 06

练习作业

通过分层练习巩固所学知识,从基础到挑战,逐步提升解题能力。

📝总结练习(3题)
总结1 循环 + 数组

题目:成绩统计

读入 n 个学生的成绩,统计平均分、最高分、最低分和及格人数。

查看参考答案与解析
C++成绩统计
#include <iostream>
using namespace std;
int main() {
    int n;
    cin >> n;
    int a[100];
    int sum = 0, mx = -1, mn = 101, pass = 0;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
        sum += a[i];
        if (a[i] > mx) mx = a[i];
        if (a[i] < mn) mn = a[i];
        if (a[i] >= 60) pass++;
    }
    cout << "平均: " << (double)sum/n << endl;
    cout << "最高: " << mx << endl;
    cout << "最低: " << mn << endl;
    cout << "及格: " << pass << endl;
    return 0;
}

解析:在一个 for 循环中同时完成累加、最值、统计,效率最高。注意初始化 mx 为 -1(最小可能值),mn 为 101(最大可能值+1)。

总结2 嵌套循环

题目:打印菱形

输入奇数 n,打印一个 n 行的菱形(由星号 * 组成)。

查看参考答案与解析
C++打印菱形
#include <iostream>
using namespace std;
int main() {
    int n;
    cin >> n;
    int mid = n / 2;
    // 上半部分(含中间行)
    for (int i = 0; i <= mid; i++) {
        for (int j = 0; j < mid - i; j++) cout << " ";
        for (int j = 0; j < 2*i+1; j++) cout << "*";
        cout << endl;
    }
    // 下半部分
    for (int i = mid-1; i >= 0; i--) {
        for (int j = 0; j < mid - i; j++) cout << " ";
        for (int j = 0; j < 2*i+1; j++) cout << "*";
        cout << endl;
    }
    return 0;
}

解析:菱形分上下两部分,每部分都是"空格 + 星号"的组合。上半部分空格递减、星号递增;下半部分反过来。

总结3 二维数组 + 嵌套循环

题目:矩阵对角线之和

输入 n×n 矩阵,求主对角线和副对角线上所有元素之和(注意中心元素不要重复计算)。

查看参考答案与解析
C++矩阵对角线之和
#include <iostream>
using namespace std;
int main() {
    int n;
    cin >> n;
    int a[100][100];
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            cin >> a[i][j];

int sum = 0;
    for (int i = 0; i < n; i++) {
        sum += a[i][i];           // 主对角线
        if (i != n-1-i)         // 避免中心元素重复
            sum += a[i][n-1-i];   // 副对角线
    }
    cout << sum << endl;
    return 0;
}

解析:主对角线满足 i==j,副对角线满足 i+j==n-1(即 j==n-1-i)。当 n 为奇数时,中心位置 i==n-1-i 会被两条对角线同时经过,需要跳过。

A基础练习(4题)
A1 for循环

题目:1到N求和

输入正整数 N,计算 1+2+3+...+N 的和。

查看参考答案
C++
int n, sum = 0;
cin >> n;
for (int i = 1; i <= n; i++) sum += i;
cout << sum << endl;

思路:把累加变量清零,用 for 循环让 1 到 N 依次加进去。

易错:sum 没初始化为 0;循环边界写成 i < n 会少加最后一项。

A2 一维数组

题目:数组最大值

读入 10 个整数,输出其中的最大值。

查看参考答案
C++
int a[10], mx = -1000000;
for (int i = 0; i < 10; i++) {
    cin >> a[i];
    if (a[i] > mx) mx = a[i];
}
cout << mx << endl;

思路:把最大值初值设得足够小,边读入边比较更新。

易错:mx 初值写成 0,如果所有数都是负数,答案会错成 0。

A3 while循环

题目:数字反转

输入一个正整数,将其各位数字反转后输出。例如输入 12345,输出 54321。

查看参考答案
C++
int n, rev = 0;
cin >> n;
while (n > 0) {
    rev = rev * 10 + n % 10;
    n /= 10;
}
cout << rev << endl;

思路:不断用 % 10 取出末位拼到反转数上,再用 / 10 缩短原数,直到 n 变成 0。

易错:循环里忘记 n /= 10 会死循环;没有考虑 n = 0 的特殊情况。

A4 二维数组

题目:矩阵加法

输入两个 n×m 矩阵 A 和 B,输出 A+B 的结果。

查看参考答案
C++
int a[100][100], b[100][100], c[100][100];
for (int i = 0; i < n; i++)
    for (int j = 0; j < m; j++)
        cin >> a[i][j];
for (int i = 0; i < n; i++)
    for (int j = 0; j < m; j++)
        cin >> b[i][j];
for (int i = 0; i < n; i++)
    for (int j = 0; j < m; j++)
        c[i][j] = a[i][j] + b[i][j];

思路:用两层循环分别读入两个矩阵,再用第三组两层循环逐元素相加输出。

易错:读入时把 n、m 用反导致数据错位;循环变量重复使用,只处理了第一行。

B提高练习(4题)
B1 排序

题目:选择排序(洛谷 P1177 排序)

读入 n 个整数,用选择排序从小到大排序后输出。

查看参考答案
C++
for (int i = 0; i < n-1; i++) {
    int minPos = i;
    for (int j = i+1; j < n; j++)
        if (a[j] < a[minPos]) minPos = j;
    if (minPos != i) {
        int t = a[i]; a[i] = a[minPos]; a[minPos] = t;
    }
}

思路:每一轮在未排序区间里找最小元素的下标,再与区间第一个位置交换。

易错:交换时直接赋值而没有用临时变量,会把一个数覆盖丢失;内层循环边界写成 j <= n 越界。

B2 二维数组

题目:矩阵乘法

输入两个矩阵 A(n×m) 和 B(m×p),计算 A×B 并输出结果矩阵。

查看参考答案
C++
// C[i][j] = sum(A[i][k] * B[k][j]) for k=0..m-1
for (int i = 0; i < n; i++)
    for (int j = 0; j < p; j++) {
        c[i][j] = 0;
        for (int k = 0; k < m; k++)
            c[i][j] += a[i][k] * b[k][j];
    }

思路:按 c[i][j] 等于 A 第 i 行与 B 第 j 列对应元素乘积之和,用三重循环累加。

易错:c[i][j] 没先清零就直接累加;忽略 A 的列数必须等于 B 的行数这一前提。

B3 嵌套循环

题目:杨辉三角(进阶版)

不存储整个三角形,只输出第 n 行的杨辉三角数值(n 可达 30)。

查看参考答案
C++
// 利用组合数公式 C(n,k) = n!/(k!*(n-k)!)
// 或用递推:C(n,k) = C(n,k-1) * (n-k+1) / k
long long c = 1;
cout << c << " ";
for (int k = 1; k <= n; k++) {
    c = c * (n - k + 1) / k;
    cout << c << " ";
}

思路:用相邻两项的递推关系 C(n,k) = C(n,k-1) * (n-k+1) / k,从 k=1 逐个算出本行。

易错:把乘除顺序写成先除后乘(c/k*(n-k+1)),整数除法会截断而丢失精度。

B4 数组 + 查找

题目:查找排序(洛谷 P1104 生日)

给定 n 个学生的姓名和生日,按生日从早到晚排序,输出排序后的姓名。

查看参考答案
C++
// 用结构体存储姓名和生日,用冒泡或选择排序
struct Student {
    string name;
    int y, m, d;
} s[100];
// 比较函数:先比年,再比月,再比日
for (int i = 0; i < n-1; i++)
    for (int j = 0; j < n-1-i; j++)
        if (s[j].y > s[j+1].y ||
            (s[j].y == s[j+1].y && s[j].m > s[j+1].m) ||
            (s[j].y == s[j+1].y && s[j].m == s[j+1].m && s[j].d > s[j+1].d))
            swap(s[j], s[j+1]);

思路:用结构体存姓名和出生年月日,比较时先比年、再比月、最后比日。

易错:比较条件只写到年份,同年出生的人顺序会乱;生日完全相同时的相对顺序没处理。

C挑战练习(4题)
C1 螺旋矩阵

题目:螺旋矩阵 II(洛谷 P5707)

输入正整数 n,生成一个 1 到 n² 的螺旋矩阵并输出。

查看参考答案
C++
// 与案例5螺旋矩阵相同,此处不再重复
// 核心:四个边界变量控制填充方向

思路:用 top、bottom、left、right 四个边界变量控制填充方向,每填完一条边就收缩一次边界。

易错:边界收缩的顺序写错会导致重复填充;n 为奇数时最中心的格子容易漏填。

C2 二维数组 + 搜索

题目:数独验证

给定一个 9×9 的数独矩阵,判断是否合法:每行、每列、每个 3×3 宫格内数字 1-9 不重复。

查看参考答案
C++
// 分别检查行、列、宫格
bool checkRow() {
    for (int i = 0; i < 9; i++) {
        bool vis[10] = {0};
        for (int j = 0; j < 9; j++)
            if (vis[a[i][j]]) return false;
            else vis[a[i][j]] = true;
    }
    return true;
}
// 列和宫格同理,宫格用 (i/3*3+j/3) 定位

思路:分别检查每行、每列、每个 3×3 宫格,用 vis 数组判断数字是否重复出现。

易错:宫格的下标算错(每格起点是 i/3*3 与 j/3*3);vis 数组没有在每个小组重新清零。

C3 Kadane算法

题目:最大子数组和(洛谷 P5808)

给定 n 个整数(可能有负数),求连续子数组的最大和。

查看参考答案
C++
// Kadane算法:O(n) 时间复杂度
int maxSum = a[0], cur = a[0];
for (int i = 1; i < n; i++) {
    cur = max(a[i], cur + a[i]);
    maxSum = max(maxSum, cur);
}
cout << maxSum << endl;

思路:cur 表示以当前位置结尾的最大子数组和——要么接上前面的,要么从当前元素重新开始,同时维护全局最大值。

易错:初值用 0,数组全为负数时答案错成 0;只更新了 cur,忘记同步更新 maxSum。

C4 约瑟夫环

题目:约瑟夫环(洛谷 P1996)

n 个人围成一圈,从第 1 个人开始报数,每次数到 m 的人出列,求出列顺序。

查看参考答案
C++
int a[100] = {0}; // 0表示在圈内,1表示已出列
int cnt = 0, pos = 0, remain = n;
while (remain > 0) {
    if (a[pos] == 0) cnt++;
    if (cnt == m) {
        cout << pos+1 << " ";
        a[pos] = 1;
        cnt = 0;
        remain--;
    }
    pos = (pos + 1) % n; // 环形
}

思路:用数组标记已出列的人,pos 在环上循环前进,数到第 m 个就出列并把计数清零。

易错:pos 前进时忘记对 n 取模导致越界;出列后忘记让 remain 减一会死循环。