分类 NOI 下的文章

0768b74d-6a93-4509-8c5e-e0b32459e9cf.png

题目1

  • 描述
    把前n个数字排列在一起,统计一下0~9各出现多少次? 编写C++程序
  • 输入
    整数n
  • 输出
    0~9 各出现次数
#include <iostream>
#include <vector>

int main() {
    int n;
    std::cout << "请输入数字n: ";
    std::cin >> n;

    // 创建一个数组来存储0到9各数字的出现次数,初始化为0
    int count[10] = {0};

    // 将前n个数字排列在一起
    for (int i = 1; i <= n; i++) {
        int num = i;
        while (num > 0) {
            // 获取最后一位数字
            int digit = num % 10;
            // 增加相应数字的计数
            count[digit]++;
            // 去掉最后一位数字
            num /= 10;
        }
    }

    // 输出统计结果
    std::cout << "0~9各数字出现的次数为:" << std::endl;
    for (int i = 0; i < 10; i++) {
        std::cout << i << ": " << count[i] << std::endl;
    }

    return 0;
}

题目2

  • 描述
    周期串,如果一个字符串可以由某个长度为k的字符串多次重复得到,则称该串以k为周期,输入一个长度不超过80的字符串,输出其最小周期,编写C++程序
  • 输入
    长度不超过80的字符串
  • 输出
    最小周期,一个整数,或者不存在
#include <iostream>
#include <string>

int findSmallestPeriod(const std::string& input) {
    int n = input.length();
    for (int k = 1; k <= n; k++) {
        if (n % k == 0) {
            bool isPeriod = true;
            for (int i = k; i < n; i++) {
                if (input[i] != input[i % k]) {
                    isPeriod = false;
                    break;
                }
            }
            if (isPeriod) {
                return k;
            }
        }
    }
    return n; // 如果没有找到周期,则返回字符串长度
}

int main() {
    std::string input;
    std::cout << "请输入一个字符串(长度不超过80): ";
    std::cin >> input;

    int smallestPeriod = findSmallestPeriod(input);

    std::cout << "最小周期为: " << smallestPeriod << std::endl;

    return 0;
}

解释说明

当我们说一个字符串具有周期性时,意味着该字符串可以由一个较短的子串重复多次组成。例如,字符串 "abcabcabc" 具有周期性,其周期为 "abc",因为它可以重复三次来构建整个字符串。

这个C++程序的目标是找到给定字符串的最小周期。最小周期是能够将字符串重复多次以构建原始字符串的最短子串长度。下面我将逐步解释程序和算法的工作原理:

  • 用户被要求输入一个字符串,该字符串的长度不超过80个字符。
  • findSmallestPeriod 函数是用于查找最小周期的核心部分。它接受一个字符串作为参数,并返回最小周期的长度。
  • 在 findSmallestPeriod 函数中,我们首先获取输入字符串的长度并将其存储在变量 n 中。
  • 接下来,我们使用一个循环来尝试不同的周期长度 k,从1到字符串的长度 n。对于每个 k 值,我们检查是否可以通过重复长度为 k 的子串来构建原始字符串。
  • 在循环中,我们首先检查 n 是否可以被 k 整除,因为只有在整除的情况下才可能存在周期性。
  • 如果 n 可以被 k 整除,那么我们进一步检查是否可以通过重复 k 长度的子串来构建原始字符串。我们使用一个嵌套循环遍历字符串的每个字符,检查是否与其对应的周期位置上的字符相同。
  • 如果所有的字符都匹配,那么我们就找到了一个周期,我们返回 k 作为最小周期的长度。
  • 如果没有找到周期,我们继续尝试下一个 k 值,直到遍历所有可能的周期长度。
  • 如果在整个循环中都没有找到周期,那么我们默认将字符串的长度 n 作为最小周期长度。
  • 最后,程序输出找到的最小周期长度。

这个算法的时间复杂度是比较高的,因为它需要尝试多个周期长度。但是,对于较短的字符串,它仍然是一个合理的方法。如果字符串很长,可能需要考虑更高效的算法。这个程序提供了一个基本的示例,演示了如何找到字符串的最小周期。

时间复杂度:当我们谈论一个算法的时间复杂度时,实际上是在讨论这个算法需要多长时间来完成任务。可以将时间复杂度想象成一个度量标准,用来衡量不同算法在处理相同问题时,谁更快或更慢。
想象一下你和你的朋友在做同样的数学题,但你们使用了不同的方法。时间复杂度就像是一种方式,帮助你们比较谁的方法更快。
时间复杂度告诉我们,当输入的数据规模增加时,算法执行所需的时间会如何增加。一个具有较低时间复杂度的算法通常会更快,因为它可以在更短的时间内处理更多的数据。
举个例子,假设你和你的朋友都要计算从1加到100的和。你选择了直接相加的方法,而你的朋友选择了使用一个数学公式(如等差数列求和公式)。尽管两种方法都可以得到正确的答案,但使用公式的方法更快,因此它的时间复杂度更低。
所以,时间复杂度就是一种衡量算法执行速度的方式,它帮助我们选择最合适的算法来解决问题,特别是在处理大量数据时,能够帮助我们选择更高效的方法。

8-BT7Tz6YA20nvp0M.png

高手可以从代码推导出语言的一般语法规则,我们尝试从这个Chatgpt提供的ATM实现,学习C++一些基础概念

使用C++ 实现ATM模拟程序

ChatGPT快速给出了一个很简单的模拟程序,可以显示余额,存款,取款基本操作,当然没有很华丽的程序界面,纯粹是命令行的操作
    #include <iostream>
    #include <iomanip>
    
    using namespace std;
    
    class ATM {
    private:
        double balance;
    
    public:
        ATM(double initialBalance) : balance(initialBalance) {}
    
        double getBalance() const {
            return balance;
        }
    
        void deposit(double amount) {
            if (amount > 0) {
                balance += amount;
                cout << "存款成功。当前余额为 $" << fixed << setprecision(2) << balance << endl;
            } else {
                cout << "无效的存款金额。" << endl;
            }
        }
    
        void withdraw(double amount) {
            if (amount > 0 && amount <= balance) {
                balance -= amount;
                cout << "取款成功。当前余额为 $" << fixed << setprecision(2) << balance << endl;
            } else {
                cout << "无效的取款金额或余额不足。" << endl;
            }
        }
    };
    
    int main() {
        double initialBalance;
        cout << "欢迎使用ATM模拟程序!" << endl;
        cout << "请输入您的初始余额: $";
        cin >> initialBalance;
    
        ATM atm(initialBalance);
    
        int choice;
        double amount;
    
        while (true) {
            cout << "请选择操作:" << endl;
            cout << "1. 查询余额" << endl;
            cout << "2. 存款" << endl;
            cout << "3. 取款" << endl;
            cout << "4. 退出" << endl;
            cin >> choice;
    
            switch (choice) {
                case 1:
                    cout << "当前余额为 $" << fixed << setprecision(2) << atm.getBalance() << endl;
                    break;
                case 2:
                    cout << "请输入存款金额: $";
                    cin >> amount;
                    atm.deposit(amount);
                    break;
                case 3:
                    cout << "请输入取款金额: $";
                    cin >> amount;
                    atm.withdraw(amount);
                    break;
                case 4:
                    cout << "感谢使用ATM模拟程序,再见!" << endl;
                    return 0;
                default:
                    cout << "无效的选项,请重新选择。" << endl;
                    break;
            }
        }
    
        return 0;
    }

这个简单的ATM模拟程序允许用户查询余额、存款、取款以及退出。用户可以根据菜单中的选项进行操作,并得到相应的反馈。请注意,这只是一个基本的示例程序,实际的ATM系统会更复杂,包括更多的功能和安全性考虑。希望这个示例有助于你理解如何编写一个简单的ATM模拟程序。

代码解释

这段代码是一个简单的C++程序,它模拟了一个基本的ATM(自动取款机)程序。ATM程序允许用户查询余额、存款、取款和退出。下面是对这段代码的解释:

  • 引入头文件和命名空间:

      #include <iostream>
      #include <iomanip>
      
      using namespace std;

    这里引入了两个C++标准库的头文件, 用于输入和输出, 用于格式化输出。using namespace std; 语句是为了简化代码,允许在代码中直接使用标准命名空间 std 中的函数和对象,而无需前缀 std::。

  • 定义ATM

      class ATM {
      private:
          double balance;
      
      public:
          ATM(double initialBalance) : balance(initialBalance) {}
      
          double getBalance() const {
              return balance;
          }
      
          void deposit(double amount) {
              if (amount > 0) {
                  balance += amount;
                  cout << "存款成功。当前余额为 $" << fixed << setprecision(2) << balance << endl;
              } else {
                  cout << "无效的存款金额。" << endl;
              }
          }
      
          void withdraw(double amount) {
              if (amount > 0 && amount <= balance) {
                  balance -= amount;
                  cout << "取款成功。当前余额为 $" << fixed << setprecision(2) << balance << endl;
              } else {
                  cout << "无效的取款金额或余额不足。" << endl;
              }
          }
      };

这里定义了一个 ATM 类,该类包含了账户余额(balance)和三个公有成员函数:ATM 构造函数,getBalance 函数用于查询余额,deposit 函数用于存款,withdraw 函数用于取款。构造函数初始化账户余额。

  • main函数
    int main() {
        double initialBalance;
        cout << "欢迎使用ATM模拟程序!" << endl;
        cout << "请输入您的初始余额: $";
        cin >> initialBalance;
        ATM atm(initialBalance);
    
        int choice;
        double amount;
    
        while (true) {
            cout << "请选择操作:" << endl;
            cout << "1. 查询余额" << endl;
            cout << "2. 存款" << endl;
            cout << "3. 取款" << endl;
            cout << "4. 退出" << endl;
            cin >> choice;
    
            switch (choice) {
                case 1:
                    cout << "当前余额为 $" << fixed << setprecision(2) << atm.getBalance() << endl;
                    break;
                case 2:
                    cout << "请输入存款金额: $";
                    cin >> amount;
                    atm.deposit(amount);
                    break;
                case 3:
                    cout << "请输入取款金额: $";
                    cin >> amount;
                    atm.withdraw(amount);
                    break;
                case 4:
                    cout << "感谢使用ATM模拟程序,再见!" << endl;
                    return 0;
                default:
                    cout << "无效的选项,请重新选择。" << endl;
                    break;
            }
        }
    
        return 0;
    }
  • main函数首先询问用户输入初始余额,并根据用户输入创建一个 ATM 对象。
  • 然后,使用一个无限循环来提供菜单选项,用户可以选择查询余额、存款、取款或退出。
  • 用户的选择由 cin 输入获取,并使用 switch 语句进行相应的处理。
  • 如果用户选择查询余额,ATM 对象的 getBalance 函数会被调用并显示当前余额。
  • 如果用户选择存款或取款,程序会要求用户输入金额,并调用相应的 deposit 或 withdraw 函数来更新余额。
  • 如果用户选择退出,程序会显示感谢消息并退出。
  • 如果用户输入无效的选项,程序会输出相应的错误消息。
这个程序模拟了一个简单的ATM功能,用户可以进行基本的查询和交易操作

拓展?

如果我们要给这个程序增加操作历史的查询功能该怎么搞?

8-gUJHttA242VwOeZ.png

C++中,你可以使用cout和cin进行格式化输出和输入。下面是几个示例,演示如何使用格式化控制符来控制输出和输入的格式。

格式化输出示例:

  • 输出浮点数时指定小数点位数:

      #include <iostream>
      #include <iomanip>
      
      using namespace std;
      
      int main() {
          double number = 123.45678;
          cout << fixed << setprecision(2) << number << endl; // 输出: 123.46
          return 0;
      }
  • 设置输出字段宽度:

      #include <iostream>
      #include <iomanip>
      
      using namespace std;
      
      int main() {
          int num1 = 123;
          int num2 = 45678;
          cout << setw(10) << num1 << setw(10) << num2 << endl; // 输出: "       123     45678"
          return 0;
      }

    格式化输入示例

  • 从用户输入中读取整数:

      #include <iostream>
      
      using namespace std;
      
      int main() {
          int num;
          cout << "请输入一个整数: ";
          cin >> num;
          cout << "您输入的整数是: " << num << endl;
          return 0;
      }
  • 从用户输入中读取浮点数:

    #include <iostream>
    #include <iomanip>
    
      using namespace std;
      
      int main() {
          double num;
          cout << "请输入一个浮点数: ";
          cin >> num;
          cout << "您输入的浮点数是: " << fixed << setprecision(2) << num << endl;
          return 0;
      }

    不同数制输出格式

  • 十进制数制输出

      #include <iostream>
      
      using namespace std;
      
      int main() {
          int decimalNumber = 123;
          cout << "十进制数:" << decimalNumber << endl; // 输出:十进制数:123
          return 0;
      }
  • 八进制数制输出:

      #include <iostream>
      #include <iomanip>
      
      using namespace std;
      
      int main() {
          int octalNumber = 0123; // 八进制数,前面加0表示
          cout << "八进制数:" << oct << octalNumber << endl; // 输出:八进制数:123
          return 0;
      }
  • 十六进制数制输出:

      #include <iostream>
      #include <iomanip>
      
      using namespace std;
      
      int main() {
          int hexadecimalNumber = 0x1A; // 十六进制数,前面加0x表示
          cout << "十六进制数:" << hex << uppercase << hexadecimalNumber << endl; // 输出:十六进制数:1A
          return 0;
      }

    cin输入出错或者异常处理

    在C++中,可以使用异常处理机制来处理cin输入的错误或异常情况。通常,cin可能会在以下情况下引发异常或错误:

  • 非法输入数据类型: 用户输入的数据类型与期望的不匹配。
  • 流状态错误: 例如,当文件结束或文件读取错误时。
  • 输入缓冲区溢出: 用户输入的数据超出了程序预期的范围。
  • 以下是一些处理cin输入异常的方法,使用了C++的异常处理机制:

      #include <iostream>
      #include <stdexcept>
      
      using namespace std;
      
      int main() {
          int num;
      
          try {
              cout << "请输入一个整数: ";
              if (!(cin >> num)) {
                  throw runtime_error("输入无效"); // 抛出运行时异常
              }
      
              // 处理正常输入
              cout << "您输入的整数是: " << num << endl;
          } catch (const exception& e) {
              cerr << "发生异常: " << e.what() << endl;
              // 清除输入缓冲区
              cin.clear();
              cin.ignore(numeric_limits<streamsize>::max(), '\n');
          }
      
          return 0;
      }

    在上述示例中,我们使用了try和catch块来处理异常情况。如果用户输入的不是整数,cin会抛出一个异常,然后我们在catch块中捕获并处理异常,输出错误消息并清除输入缓冲区。

    在异常处理中,你可以采取适当的措施,例如重新请求用户输入、输出错误消息、记录错误等,以便更好地处理cin输入引发的异常。

    请注意,异常处理是一种处理输入错误的一种方法,但不是唯一的方法。你还可以使用其他方式来验证和处理输入,根据具体需求来选择最合适的方法。

8-FCxNfOuUU6NV7V5.png

开灯问题

  • 问题描述:

    n盏灯,编号1~n,第一个人把所有灯打开,第二个人按下所有编号为2的倍数的开关,第三个人按下所有编号为3的倍数的开关,以此类推,一共有k个人,文最后有哪些等开着,输入n和k,输出开着的灯的编号: k<=n<=1000, 编写C++程序?

例子1:
输入:

10 5

输出:

1 4 6 7 8 10

例子2:
输入:

20 7

输出:

1 4 8 10 11 12 13 14 15 16 17 19

编写一个C++程序来模拟这个问题,根据输入的n和k计算最后哪些灯开着

#include <iostream>
#include <vector>

using namespace std;

int main() {
    int n, k;
    cout << "请输入灯的数量 n:";
    cin >> n;
    cout << "请输入操作次数 k:";
    cin >> k;

    vector<bool> lights(n, false);

    for (int i = 1; i <= k; ++i) {
        for (int j = i - 1; j < n; j += i) {
            lights[j] = !lights[j];
        }
    }

    cout << "最后开着的灯的编号是:";
    for (int i = 0; i < n; ++i) {
        if (lights[i]) {
            cout << i + 1 << " ";
        }
    }
    cout << endl;

    return 0;
}

这个程序首先读取输入的灯的数量n和操作次数k,然后创建一个bool型的vector来表示每盏灯的状态,初始状态都是关着的(false)。

然后,程序使用两层循环来模拟每个人的操作。外层循环迭代k次,模拟k个人的操作,内层循环按照当前人的编号(i)来切换对应编号的灯的状态。

最后,程序遍历vector,找出开着的灯的编号并输出。

对于比赛,程序应该严格按照题目要求,不能有多余的输入和输出; 这里为了方便演示,多了一些额外的输入与输出提示

蛇形填数

  • 问题描述:

    蛇形填数问题:在nn的方阵里面填入1, 2, ..., nn,
    要求填成蛇形,从左上角第一个格子开始,第一行开始,然后最后一列,最后一行,依次类推,构成蛇形,编写C++程序实现

例子1:
输入:

4

输出:

1       2       3       4
12      13      14      5
11      16      15      6
10      9       8       7

例子2:
请输入n的值:10
蛇形填数结果:
1 2 3 4 5 6 7 8 9 10
36 37 38 39 40 41 42 43 44 11
35 64 65 66 67 68 69 70 45 12
34 63 84 85 86 87 88 71 46 13
33 62 83 96 97 98 89 72 47 14
32 61 82 95 100 99 90 73 48 15
31 60 81 94 93 92 91 74 49 16
30 59 80 79 78 77 76 75 50 17
29 58 57 56 55 54 53 52 51 18
28 27 26 25 24 23 22 21 20 19

你可以输入不同的n和k来测试程序的功能。这个程序适用于n和k的范围在1到1000之间的情况。

#include <iostream>
#include <vector>

using namespace std;

int main() {
    int n;
    cout << "请输入n的值:";
    cin >> n;

    vector<vector<int>> matrix(n, vector<int>(n, 0));

    int num = 1;
    int row = 0, col = 0;
    int direction = 0; // 0: 向右,1: 向下,2: 向左,3: 向上

    while (num <= n * n) {
        matrix[row][col] = num++;
        
        // 向右
        if (direction == 0) {
            if (col + 1 < n && matrix[row][col + 1] == 0) {
                col++;
            } else {
                direction = 1;
                row++;
            }
        }
        // 向下
        else if (direction == 1) {
            if (row + 1 < n && matrix[row + 1][col] == 0) {
                row++;
            } else {
                direction = 2;
                col--;
            }
        }
        // 向左
        else if (direction == 2) {
            if (col - 1 >= 0 && matrix[row][col - 1] == 0) {
                col--;
            } else {
                direction = 3;
                row--;
            }
        }
        // 向上
        else if (direction == 3) {
            if (row - 1 >= 0 && matrix[row - 1][col] == 0) {
                row--;
            } else {
                direction = 0;
                col++;
            }
        }
    }

    // 输出结果
    cout << "蛇形填数结果:" << endl;
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            cout << matrix[i][j] << "\t";
        }
        cout << endl;
    }

    return 0;
}

8-es7xztRr0fgvXfT.png

挺不错的C++入门教程www.learncpp.com

对于将C++编程作为比赛工具学生来说,快速入门,不需要太深入语言的细节,各种高阶的语言特性、复杂的应用设计等高阶概念,能够比较熟练C++常规的语言特性,构建比赛程序足以

要快速入门,和学习大部分语言类似,从开发环境搭建、hello world程序到调试工具的使用

  • 开发环境搭建
  • hello world
  • 调试工具(IDE)
这里就将语言背景介绍略过了,有心人可以从网络搜索到各种背景知识!

C++程序开发流程

2023-09-16T02:45:15.png

上图比较概括描述了C++程序是如何被开发出来的

  • Step 1: 定义我们需要解决的问题
  • Step 2: 设计一个解决方案
  • Step 3: 编写一个程序,实现这个方案
  • Step 4: 编译这个程序
  • Step 5: 链接目标文件
  • Step 6: 测试程序
  • Step 7: 调试程序(如果有问题,不符合预期等)

    #include

    int main()
    {

      std::cout << "Colored text!";
      return 0;

    }

一些工具的介绍

  • compiler 编译器
  • linker 链接器
  • libraries 库

如果我们有多个*.cpp文件,编译器会生成的哦个object(目标)文件,如下图:
2023-09-16T02:50:26.png

链接器程序将目标文件整合成可执行程序,比如Calculator.exe

IDE(Integrated Development Enironments) 集成开发环境

从命令行到图形化开发环境,可以使用的工具很多,初学者开始可能不习惯命令行,GUI(Graphics User
Interface)图形化用户界面可能更友好一些,但是对于专业开发人员,掌握命令行的工具使用,很有必要!
  • Windows上有微软的Visual Studio 版本,
    免费的社区版,可以供学习使用;商用的适合专业开发人员
    2023-09-16T02:54:36.png
  • Code::Blocks(跨平台使用,Linux or Windows)
    Code::Blocks官方下载链接
    CodeBlocks-IDE.png
Window上下载安装比较直观;Linux上也有比较简单的安装步骤,并不复杂

为每个程序创建独立的project

在Code::Blocks里面只有创建的project,才能使用调试功能;独立建立的文件无法使用内建的调试插件
  • 在Code::Blocks里面创建一个工程:

2023-09-16T03:43:11.png

  • 需要给工程选择一个目录,用来存储工程、程序文件:
    2023-09-16T03:43:35.png
  • 建好之后,在IDE里面有个专门工程管理窗口,列出工程相关的信息、文件列表等:
    2023-09-16T03:44:12.png
    默认建立的工程代码:

    #include

    int main()
    {

      std::cout << "Hello, world!";
      return 0;

    }

  • 编译执行,快捷键Ctrl-F9, 或者Build菜单->Build

2023-09-16T04:05:02.png