中 · 二重循环

矩阵乘法

每个输出格子都是一次点积:A 的第 i 行和 B 的第 j 列手牵手走,中间维度 m 是它们唯一共享的东西。把三个角色分清 —— A 是 n×m,B 是 m×k,C 是 n×k —— 代码自己就写出来了。

一句话答案<code>C[i][j] = 对 t 求和 A[i][t] * B[t][j]</code> —— 三重循环,C 逐格清零,维度 n×m、m×k → n×k。

题目

输入: 第一行是 n、m、k(每个 1 ≤ ≤ 10)。接下来 n 行、每行 m 个整数:矩阵 A。然后 m 行、每行 k 个整数:矩阵 B。取值 −100 到 100。

输出: n 行、每行 k 个整数:乘积 A×B,数字间单个空格。

样例:

输入输出
2 2 2
1 2
3 4
5 6
7 8
19 22
43 50
1 1 1
3
4
12

手算第一个格子:C[1][1] = 1·5 + 2·7 = 19。行碰上列,逐对相乘、求和 —— 会算一个格子,循环就是把所有格子都算一遍。

你的解答

main.cpp — 你的解答
「运行我的输入」用的自定义输入

判题在浏览器本地运行。死循环会在 3 秒后被自动中断 —— 但还是确保每个循环都有出口。代码随打随存。

卡住了?提示

提示 1 — 每个格子自己清零

C 是全局数组,而本站引擎的全局数组不会自动清零(真实 C++ 会,我们的解释器不会)。无论如何这都是好习惯:在 i/j 循环内第一行写 C[i][j] = 0;,再用 += 累加。凡涉及内存,显式总比隐式可靠。

提示 2 — 中间那个下标是合页

求和下标 t 走的是 m —— A 的列数、B 的行数。常见的手滑写成 A[t][i] 或 B[j][t]:都能编译,全都是错的。把形状念一遍:A 是 n 行 m 列,B 是 m 行 k 列 —— 正是这个共享的 m 让乘法有意义。

提示 3 — 打印要有判题同款的纪律

行内空格隔开、行尾换行、每行最后一个数后面不带空格:在每个非首个值之前打印分隔符 —— 和 冒泡排序 里的做法一样。输出格式是题面的一部分,不是细节。

题解(先自己试试!)
#include <iostream>
using namespace std;

int A[12][12], B[12][12], C[12][12];

int main() {
    int n, m, k;
    cin >> n >> m >> k;

    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            cin >> A[i][j];
    for (int i = 0; i < m; i++)
        for (int j = 0; j < k; j++)
            cin >> B[i][j];

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < k; j++) {
            C[i][j] = 0;
            for (int t = 0; t < m; t++) {
                C[i][j] += A[i][t] * B[t][j];
            }
        }
    }

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < k; j++) {
            cout << C[i][j];
            if (j < k - 1) cout << " ";
        }
        cout << endl;
    }
    return 0;
}

你刚写的三重循环,一字不差就是 BLAS 库优化到每秒十亿次运算的东西 —— 数学从不改变,改变的只是内存布局。二维三件套你已集齐:求和(矩阵求和)、换下标(矩阵转置)、组合(本题)。网格这条线剩下的,就是向矩形提问 —— 二维区间求和。

深度解析

为什么这样做对

每个输出格子是一次点积:A 的第 i 行对着 B 的第 j 列,沿共享维度 m 求和。三重循环就是把定义念出声 —— n×k 个格子,每格 m 次乘加。没有东西需要证明;难点纯粹是让三个维度各就各位。

代价

O(n·m·k) —— 本题 10×10×10 = 一千次乘加。同样的循环放到 1000×1000 就是十亿次运算,这正是 BLAS 这类库存在的理由:数学不变,变的是内存布局。

常见错法

  • 点积里角色互换:A[t][i] 或 B[j][t] 都能编译、都是错的。合页是 A[i][t] * B[t][j] —— A 的行、B 的列。
  • 信任全局自动清零:本引擎的全局数组不清零,循环内那句 C[i][j] = 0; 是强制的,不是风格。
  • 输出纪律:除每行第一个外,每个值之前打分隔符,行尾换行 —— 判题比对的是字符,不是心意。

下一步

二维三件套你已集齐:求和(矩阵求和)、换下标(矩阵转置)、组合(本题)。网格的下一项技能是向矩形提问:二维区间求和。

记住这两句

  • C[i][j] = Σ A[i][t]·B[t][j] —— 共享的 m 让乘法有意义。
  • 累加格子显式清零;永远别指望全局数组天生干净。
🐞 这段是错的 —— 点开,亲手让它挂一次

内层循环跑到 k 而不是 m —— 只有方阵测试肯原谅它

#include <iostream>
using namespace std;

int A[12][12], B[12][12], C[12][12];

int main() {
    int n, m, k;
    cin >> n >> m >> k;
    for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) cin >> A[i][j];
    for (int i = 0; i < m; i++) for (int j = 0; j < k; j++) cin >> B[i][j];
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < k; j++) {
            C[i][j] = 0;
            for (int t = 0; t < k; t++) {   // bug: k instead of m
                C[i][j] += A[i][t] * B[t][j];
            }
        }
    }
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < k; j++) {
            cout << C[i][j];
            if (j < k - 1) cout << " ";
        }
        cout << endl;
    }
    return 0;
}