3 条题解

  • 0
    @ 2025-12-5 11:55:09

    暴力模拟法

    好的,教练来提供一种最直观、最不需要动脑筋数学公式的暴力模拟法

    这种方法的核心思想是:不要去想取模公式,超出了就减去 N,不够了就加上 N,直到下标合法为止。

    这种方法特别适合初学者理解“环”的概念,且在本题的数据范围(N,M,k100N, M, |k| \le 100)下完全能通过,不会超时。

    暴力解法思路

    1. 用一个变量 pos 记录当前位置下标。
    2. 每次读入一个移动步数 k,直接执行 pos = pos + k
    3. 处理越界(把数组变成环):
      • 如果 pos 太大了N\ge N):说明走过了终点,那就不断减去 N,直到它回到 [0,N1][0, N-1] 范围内。
      • 如果 pos 变成负数了<0< 0):说明反向走过头了,那就不断加上 N,直到它变成正数且在 [0,N1][0, N-1] 范围内。

    C++14 代码实现

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    int main() {
        // 1. 输入处理
        int n, m;
        if (!(cin >> n >> m)) return 0;
    
        vector<int> a(n);
        for (int i = 0; i < n; i++) {
            cin >> a[i];
        }
    
        // 2. 暴力模拟游走
        int pos = 0; // 初始在下标 0
    
        for (int i = 0; i < m; i++) {
            int k;
            cin >> k;
    
            // 简单粗暴:直接加
            pos = pos + k;
    
            // 修正:如果跑太远了(下标超过 N-1),就拉回来
            // 比如 N=5,pos跑到 7,减去 5 变成 2
            while (pos >= n) {
                pos = pos - n;
            }
    
            // 修正:如果跑到负数去了,就推回去
            // 比如 N=5,pos跑到 -1,加上 5 变成 4
            while (pos < 0) {
                pos = pos + n;
            }
        }
    
        // 3. 输出结果
        cout << a[pos] << endl;
    
        return 0;
    }
    

    为什么这个方法可行?(复杂度分析)

    • 题目数据范围N100N \le 100, 每次移动步数 k100|k| \le 100
    • 最坏情况
      • 如果 k100N1,那么 while (pos >= n) 循环会执行 100 次。
      • 总共有 M (M100M \le 100) 个指令。
      • 总运算次数大约是 100×100=10,000100 \times 100 = 10,000 次。
    • 结论:计算机每秒可以运行 10810^8 次运算,一万次运算对它来说就像眨眼一样快。所以这个暴力法是绝对安全的。

    教学意义

    对于初学 OI 的同学,如果一时想不通 ((pos + k) % n + n) % n 这个公式,用 while 循环进行加减修正是一个非常棒的替代策略。它逻辑清晰,不易写错,体现了“用计算机的算力换取人脑的逻辑简化”这一思想。

    • 0
      @ 2025-12-5 11:49:23

      这是一个用于生成“环状序列游走”题目测试数据的完整 C++ 代码。

      它包含了一个**标准解答(Solution)用来生成正确的 .out 文件,以及一个数据生成器(Generator)**用来构造覆盖各种边缘情况的 .in 文件。

      数据点设计策略

      为了确保测试数据的有效性,设计了以下 10 个测试点:

      • #1 (样例):题目中给出的样例,用于基础验证。
      • #2 (纯顺时针)kk 全部为正数,测试基础取模。
      • #3 (纯逆时针)kk 全部为负数,测试负数取模逻辑 (idx + k) % n + n) % n
      • #4 (原地不动)N=1N=1,无论怎么走都应该停在下标 0。这是极端的边界情况。
      • #5 (大步流星)k>N|k| > N,步数超过数组长度,测试是否能正确处理多圈回绕。
      • #6 (左右横跳)kk 正负交替,测试累加逻辑的稳定性。
      • #7 (零步测试):包含 k=0k=0 的情况(虽然题目暗示 k0k \neq 0,但程序应能处理原地不动)。
      • #8 (最大数据 1)N=100,M=100N=100, M=100,常规随机。
      • #9 (最大数据 2)N=100,M=100N=100, M=100,数值较大。
      • #10 (压力混合):各种情况的混合,确保没有逻辑漏洞。

      Generator 代码 (gen.cpp)

      你可以直接保存为 gen.cpp,编译运行即可生成 1.in/1.out10.in/10.out

      #include <bits/stdc++.h>
      using namespace std;
      
      // ==========================================
      // Part 1: 标准解答 (Standard Solution)
      // 用于根据 input 生成正确的 output
      // ==========================================
      int solve(int n, int m, const vector<int>& a, const vector<int>& steps) {
          int current_pos = 0; // 初始位置 0
          
          for (int k : steps) {
              // 核心公式:处理正负数移动及多圈回绕
              // 1. (current_pos + k) 可能为负
              // 2. % n 后可能仍为负 (取决于语言特性,C++中负数取模仍为负)
              // 3. + n 保证为正
              // 4. 再 % n 处理正溢出
              current_pos = ((current_pos + k) % n + n) % n;
          }
          
          return a[current_pos];
      }
      
      // ==========================================
      // Part 2: 数据生成器工具 (Generator Tools)
      // ==========================================
      mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
      
      // 生成 [L, R] 范围内的随机整数
      int random_int(int L, int R) {
          return uniform_int_distribution<int>(L, R)(rng);
      }
      
      // 生成指定模式的数据
      void generate_case(int case_id) {
          int n, m;
          vector<int> a;
          vector<int> steps;
      
          // 根据测试点 ID 构造不同特性的数据
          switch (case_id) {
              case 1: // 题目样例
                  n = 5; m = 4;
                  a = {10, 20, 30, 40, 50};
                  steps = {2, -1, 4, -2};
                  break;
      
              case 2: // 纯顺时针 (k > 0)
                  n = 10; m = 5;
                  for(int i=0; i<n; i++) a.push_back(random_int(1, 100));
                  for(int i=0; i<m; i++) steps.push_back(random_int(1, 5));
                  break;
      
              case 3: // 纯逆时针 (k < 0)
                  n = 10; m = 5;
                  for(int i=0; i<n; i++) a.push_back(random_int(1, 100));
                  for(int i=0; i<m; i++) steps.push_back(random_int(-5, -1));
                  break;
      
              case 4: // 边界情况:只有 1 个元素
                  n = 1; m = 20;
                  a.push_back(999);
                  for(int i=0; i<m; i++) steps.push_back(random_int(-10, 10));
                  break;
      
              case 5: // 步数大于长度 (|k| > N)
                  n = 5; m = 10;
                  for(int i=0; i<n; i++) a.push_back(i * 10);
                  for(int i=0; i<m; i++) {
                      // 生成绝对值比 n 大的步数
                      int k = random_int(6, 20); 
                      if (random_int(0, 1)) k = -k;
                      steps.push_back(k);
                  }
                  break;
      
              case 6: // 左右横跳 (正负交替)
                  n = 20; m = 50;
                  for(int i=0; i<n; i++) a.push_back(random_int(1, 1000));
                  for(int i=0; i<m; i++) {
                      int k = random_int(1, 10);
                      if (i % 2 == 1) k = -k;
                      steps.push_back(k);
                  }
                  break;
      
              case 7: // 包含原地不动 (k = 0)
                  n = 50; m = 50;
                  for(int i=0; i<n; i++) a.push_back(random_int(1, 1000));
                  for(int i=0; i<m; i++) steps.push_back(random_int(-5, 5));
                  break;
      
              case 8: // 满数据范围 (N=100, M=100) - 随机
                  n = 100; m = 100;
                  for(int i=0; i<n; i++) a.push_back(random_int(1, 1000));
                  for(int i=0; i<m; i++) steps.push_back(random_int(-100, 100));
                  break;
      
              case 9: // 满数据范围 - 大步数
                  n = 100; m = 100;
                  for(int i=0; i<n; i++) a.push_back(random_int(1, 1000));
                  for(int i=0; i<m; i++) {
                      // 生成接近 100 的步数
                      steps.push_back(random_int(-100, 100));
                  }
                  break;
                  
              case 10: // 混合压力测试
                  n = random_int(80, 100); 
                  m = random_int(80, 100);
                  for(int i=0; i<n; i++) a.push_back(random_int(1, 1000));
                  for(int i=0; i<m; i++) steps.push_back(random_int(-100, 100));
                  break;
      
              default:
                  break;
          }
      
          // ------------------------------------
          // 生成文件逻辑
          // ------------------------------------
          string in_file = to_string(case_id) + ".in";
          string out_file = to_string(case_id) + ".out";
      
          // 1. 写入 .in 文件
          ofstream fin(in_file);
          fin << n << " " << m << "\n";
          for(int i=0; i<n; i++) fin << a[i] << (i==n-1 ? "" : " ");
          fin << "\n";
          for(int i=0; i<m; i++) fin << steps[i] << (i==m-1 ? "" : " ");
          fin << "\n";
          fin.close();
      
          // 2. 计算答案并写入 .out 文件
          int ans = solve(n, m, a, steps);
          ofstream fout(out_file);
          fout << ans << "\n";
          fout.close();
      
          cout << "Generated Case " << case_id << ": N=" << n << ", M=" << m << endl;
      }
      
      int main() {
          // 循环生成 1-10 号测试点
          for (int i = 1; i <= 10; ++i) {
              generate_case(i);
          }
          cout << "All test cases generated successfully!" << endl;
          return 0;
      }
      

      如何使用

      1. 编译: 在终端或命令行中输入:
        g++ gen.cpp -o gen -std=c++14
        
      2. 运行
        ./gen  # Windows 下是 gen.exe
        
      3. 结果: 当前目录下会生成 1.in, 1.out ... 10.in, 10.out 共 20 个文件。
        • .in 文件包含输入数据。
        • .out 文件包含对应的正确答案。

      检查建议

      你可以打开 4.in(单元素数组测试),确认内容如下:

      1 20
      999
      ... (20个随机指令)
      

      并检查 4.out,因为数组只有一个元素,无论怎么走,最终结果必然是 999。如果你的标准代码输出了其他值,说明取模逻辑有 bug(例如对 1 取模出错了)。这个生成器里的 solve 函数已经使用了最稳健的写法 ((pos + k) % n + n) % n,确保数据是正确的。

      • 0
        @ 2025-12-5 11:39:59

        【解题提示】

        这道题唯一的坑点在于 k 为负数时。 在 C++ 中-2 % 5 的结果是 -2,而不是我们期望的循环下标 3。 为了解决这个问题,我们需要使用课件中提到的“万能取模公式”(用于确保mod运算结果不为负的技巧:把mod的结果加上1倍模数再取模

        $$\text{index} = ( (\text{current} + \text{step}) \% N + N ) \% N $$

        这个公式无论是正向越界还是反向越界,都能计算出正确的下标。

        【参考代码 (C++14)】

        #include <iostream>
        #include <vector>
        
        using namespace std;
        
        int main() {
            // 1. 基础 I/O 设置
            int n, m;
            cin >> n >> m;
        
            vector<int> a(n);
            for (int i = 0; i < n; i++) {
                cin >> a[i];
            }
        
            // 2. 模拟游走
            int current_pos = 0; // 初始位置
            for (int i = 0; i < m; i++) {
                int k;
                cin >> k;
                
                // 核心公式:处理正负数移动
                // 写法一:直接加 k,然后用通用公式修正
                // % n 可能得到负数,所以要再 + n 再 % n
                current_pos = ((current_pos + k) % n + n) % n;
                
                // 写法二(分情况讨论,适合初学者理解):
                // if (k > 0) current_pos = (current_pos + k) % n;
                // else current_pos = (current_pos + k + n * 100) % n; // 加上足够的 n 保证为正
            }
        
            // 3. 输出结果
            cout << a[current_pos] << endl;
        
            return 0;
        }
        
        • 1

        信息

        ID
        19263
        时间
        1000ms
        内存
        32MiB
        难度
        10
        标签
        (无)
        递交数
        1
        已通过
        1
        上传者