3 条题解

  • 0
    @ 2026-6-9 15:24:16

    这里为你提供这道题的标准题解代码。

    我们将从符合初学者直觉的“纯模拟递增版”开始,然后过渡到代码最精简的“数学取模最优版”。每一版代码都可以直接复制运行,并且完全符合 NOIP C++14 的竞赛标准。


    版本一:纯模拟递增版 (直观但代码略长)

    思路: 很多刚学 C++ 的同学可能对 % (取模) 运算还不熟悉,但他们知道字符是可以加一的 ('A' + 1 变成 'B')。所以这个版本的思路是:手动维护每一行的开头字母,然后在每一行内部手动维护当前输出的字母,一旦超过 'Z',就强行把它拨回 'A'

    #include <iostream>
    
    using namespace std;
    
    int main() {
        // 优化标准输入输出流速度,NOIP常用起手式
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
    
        int n;
        if (!(cin >> n)) return 0;
    
        // 记录每一行的第一个字母,初始为 'A'
        char row_start = 'A'; 
    
        for (int i = 0; i < n; ++i) { // 外层循环:控制打印 n 行
            
            // 每一行开始时,把当前字母设为该行的开头字母
            char current = row_start; 
    
            for (int j = 0; j < n; ++j) { // 内层循环:控制每一行打印 n 个字符
                cout << current; // 输出当前字母
                
                // 【关键点1】准备下一个字母
                current++; 
                
                // 【易错点】边界重置。一旦字母超过了 'Z',立刻回到 'A'
                // 注意:必须用单引号括起来表示字符,如 'Z'
                if (current > 'Z') {
                    current = 'A';
                }
            }
            cout << "\n"; // 【关键点2】一行打印完毕后必须换行
    
            // 准备下一行的开头字母:也是往后推一位
            row_start++;
            if (row_start > 'Z') {
                row_start = 'A';
            }
        }
    
        return 0;
    }
    

    版本一复杂度分析

    • 时间复杂度: 使用了外层 nn 次、内层 nn 次的双重循环,内部只有简单的加法和 if 比较操作。时间复杂度为 O(n2)O(n^2)
    • 空间复杂度: 只用了几个 charint 变量,没有开辟数组。空间复杂度为 O(1)O(1)

    版本二:数学取模最优版 (竞赛标准写法)

    思路: 像我们在草稿纸上推导的那样,第 ii 行第 jj 列(行号和列号从 00 开始)的字符,其实就是从 'A' 开始往后平移了 i+ji + j 步。为了防止平移出界,我们利用 % 26 来让它在 0250 \sim 25 之间无限循环。这是最优雅、最推荐的写法。

    #include <iostream>
    
    using namespace std;
    
    int main() {
        // 优化输入输出
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
    
        int n;
        if (!(cin >> n)) return 0;
    
        // 【关键点1】行号 i 和列号 j 都要从 0 开始遍历!
        // 因为偏移量公式 (i+j) 的基准点是 (0+0)=0 偏移,也就是不偏移,正好输出 'A'
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                
                // 【关键点2 / 核心公式】
                // (i + j) 是总偏移量步数
                // (i + j) % 26 把步数限制在 0 ~ 25 的周期内
                // 'A' + 偏移量 得到对应的 ASCII 码
                // (char) 将其强制转换回字符类型输出
                cout << (char)('A' + (i + j) % 26);
                
            }
            cout << "\n"; // 每行输出后换行
        }
    
        return 0;
    }
    

    版本二复杂度分析思考过程

    • 时间复杂度: 同样是双重循环输出 n2n^2 个字符,没有冗余计算。时间复杂度 O(n2)O(n^2)
    • 空间复杂度: O(1)O(1),常数级空间,直接在 cout 流中输出。
    • 从渐近复杂度来看,由于至少要输出 n2n^2 个字符,所以时间复杂度下限就是 O(n2)O(n^2),已经无法再降低了。

    💡 教练的进阶优化探讨 (如果这题 nn 不是 40 而是 10000 呢?)

    作为未来的金牌选手,我们要思考得更深一点:

    如果 n10000n \le 10000,打印 10810^8 个字符,版本二会不会超时?

    • 瓶颈 1: 在现代 CPU 下,%(取模/除法)操作的底层指令周期,要比单纯的加法和 if 比较慢好几倍!所以在大数据量下,“纯模拟递增版”的运行速度其实可能会比“取模版”更快。
    • 极致优化策略(滑动窗口/空间换时间): 既然每一行只是字母表的平移,我们可以预先在内存里生成一个超级长串:"ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ..."。 当要打印某一行时,不用一个一个字符算,直接用 C++ 指针或 string::substr,从预制字符串的第 imod26i \bmod 26 个位置开始,一口气截取 nn 个字符输出。 这样就把原本的双层循环计算,降维打击成了底层的内存批量拷贝(Memory Copy),速度会提升几个数量级!

    不过就本题 n40n \le 40 而言,以上两种代码耗时都在 1 毫秒内,你只需要掌握版本二这个最基础也是最重要的取模思维即可!祝你早日 AC!

    信息

    ID
    13927
    时间
    1000ms
    内存
    128MiB
    难度
    1
    标签
    递交数
    1
    已通过
    1
    上传者