2 条题解

  • 0
    @ 2025-12-15 11:28:20

    这是一个可以生成 10 组测试数据(.in.out 文件)的 C++ 程序。它包含了数据生成逻辑标准解法

    功能特点

    1. 覆盖全面:包含样例、小规模数据、大规模互不相同数据、大规模重复数据、升序/降序特殊情况、所有身高相同等边界情况。
    2. 自动求解:内置了针对本题的最优解算法,自动生成对应的 .out 答案文件,确保数据正确性。
    3. 文件生成:直接在当前目录下生成 1.in~10.in1.out~10.out

    数据生成器代码 (generator.cpp)

    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <fstream>
    #include <string>
    #include <random>
    #include <numeric> // for std::iota
    #include <set>
    
    using namespace std;
    
    // ==========================================
    // 1. 标准解法函数 (用于生成 .out 文件)
    // ==========================================
    void solve_and_write(const string& in_path, const string& out_path) {
        ifstream cin(in_path);
        ofstream cout(out_path);
    
        int N;
        if (!(cin >> N)) return;
    
        vector<int> heights(N);
        for (int i = 0; i < N; ++i) {
            cin >> heights[i];
        }
    
        int M;
        if (!(cin >> M)) return;
    
        // q 用于维护当前队伍中所有同学的身高,保持有序
        vector<int> q;
        q.reserve(N);
    
        for (int i = 0; i < M; ++i) {
            int x;
            cin >> x;
            int h = heights[x]; // 当前新入队同学的身高
    
            // 查找第一个大于 h 的位置
            auto it = upper_bound(q.begin(), q.end(), h);
            
            int p = distance(q.begin(), it);
            int k = q.size();
    
            if (p == k) {
                cout << 0 << "\n";
                q.push_back(h);
            } else {
                // 统计受影响区间内数值变化的次数
                int swaps = 1;
                for (int j = p + 1; j < k; ++j) {
                    if (q[j] != q[j-1]) {
                        swaps++;
                    }
                }
                cout << swaps << "\n";
                q.insert(it, h);
            }
        }
    }
    
    // ==========================================
    // 2. 数据生成逻辑
    // ==========================================
    mt19937 rng(1337); // 固定种子,保证生成数据可复现
    
    int rand_int(int l, int r) {
        uniform_int_distribution<int> dist(l, r);
        return dist(rng);
    }
    
    void generate_case(int id) {
        string in_file = to_string(id) + ".in";
        string out_file = to_string(id) + ".out";
        
        ofstream cout(in_file);
    
        int N, M;
        vector<int> heights;
        vector<int> queries;
    
        // 构造不同类型的测试点
        switch (id) {
            case 1: // 样例 1:小数据,互不相同
                N = 5;
                heights = {170, 165, 168, 160, 175};
                M = 4;
                queries = {0, 3, 2, 1};
                break;
            case 2: // 样例 2:小数据,有重复
                N = 4;
                heights = {20, 20, 20, 10};
                M = 4;
                queries = {0, 1, 2, 3};
                break;
            case 3: // 50% 数据限制:N=500,身高互不相同
                N = 500;
                M = 500;
                heights.resize(N);
                iota(heights.begin(), heights.end(), 1); // 1, 2, ..., N
                shuffle(heights.begin(), heights.end(), rng);
                break;
            case 4: // 50% 数据限制:N=2000,身高互不相同,升序输入
                N = 2000;
                M = 2000;
                heights.resize(N);
                iota(heights.begin(), heights.end(), 10000); // 10000, 10001...
                break;
            case 5: // 50% 数据限制:N=2000,身高互不相同,降序输入
                N = 2000;
                M = 2000;
                heights.resize(N);
                iota(heights.begin(), heights.end(), 1);
                reverse(heights.begin(), heights.end());
                break;
            case 6: // 100% 数据:N=100,所有身高都相同 (Case 6~10允许重复)
                N = 100;
                M = 100;
                heights.assign(N, 170);
                break;
            case 7: // 100% 数据:N=2000,只有两种身高
                N = 2000;
                M = 2000;
                heights.resize(N);
                for(int i=0; i<N; ++i) heights[i] = (rng() % 2) ? 160 : 180;
                break;
            case 8: // 100% 数据:N=2000,少量重复 (值域小)
                N = 2000;
                M = 2000;
                heights.resize(N);
                for(int i=0; i<N; ++i) heights[i] = rand_int(150, 160); // 只有11种可能
                break;
            case 9: // 100% 数据:N=2000,大量重复 (离散分布)
                N = 2000;
                M = 2000;
                heights.resize(N);
                for(int i=0; i<N; ++i) heights[i] = (rng() % 10) * 10 + 100; // 100, 110...190
                break;
            case 10: // 100% 数据:N=2000,随机大整数 (一般情况)
                N = 2000;
                M = 2000;
                heights.resize(N);
                for(int i=0; i<N; ++i) heights[i] = rand_int(1, 2000000);
                break;
            default:
                break;
        }
    
        // 生成查询序列 (保证不重复点名)
        if (id > 2) { // 样例1和2已经手动指定了查询
            vector<int> p(N);
            iota(p.begin(), p.end(), 0);
            shuffle(p.begin(), p.end(), rng);
            queries.assign(p.begin(), p.begin() + M);
        }
    
        // 写入 .in 文件
        cout << N << "\n";
        for (int i = 0; i < N; ++i) {
            cout << heights[i] << (i == N - 1 ? "" : " ");
        }
        cout << "\n" << M << "\n";
        for (int i = 0; i < M; ++i) {
            cout << queries[i] << "\n";
        }
        cout.close();
    
        // 生成 .out 文件
        solve_and_write(in_file, out_file);
        
        std::cout << "Generated Case " << id << " successfully." << endl;
    }
    
    int main() {
        // 生成 1 到 10 号测试点
        for (int i = 1; i <= 10; ++i) {
            generate_case(i);
        }
        return 0;
    }
    

    使用说明

    1. 将上述代码保存为 generator.cpp
    2. 使用 C++ 编译器编译运行(例如 g++ generator.cpp -o gen && ./gen)。
    3. 程序运行结束后,当前目录下会出现 1.in ~ 10.in 以及对应的 1.out ~ 10.out

    测试点覆盖详情

    • Case 1-2: 题目给定的样例,用于验证基础逻辑。
    • Case 3-5: 针对 50%50\% 的数据限制(互不相同)。
      • Case 3: 随机互不相同。
      • Case 4: 已排序的输入(可能触发某些排序算法的最优/最差情况)。
      • Case 5: 逆序输入。
    • Case 6-10: 针对 100%100\% 的数据限制(允许重复)。
      • Case 6: 极端情况,所有身高完全相同(应输出全 0)。
      • Case 7: 只有两种身高,测试对重复段的处理。
      • Case 8: 密集的小范围随机数。
      • Case 9: 离散的重复数。
      • Case 10: 满数据范围的随机大数。
    • 0
      @ 2025-12-15 11:12:29

      我们可以用 更直观、更符合直觉的“空位填补”和“冒泡排序” 的思想来解释这个最少交换次数的问题。

      符合中学知识范畴的解释

      1. 问题转化:排好队的插队问题

      想象现在的队伍已经按照身高从矮到高排好了(就像做操排队一样)。 这时候,新同学(身高为 HH)来了,他一开始站在队伍的最后面

      • 情况一:他比队尾的人还高(或一样高)。 那他直接站在最后就行了,队伍依然是有序的。 交换次数:0

      • 情况二:他比较矮,需要插队到中间某个位置。 假设正确的位置应该是第 PP 个位置(下标从 0 开始)。 为了让他站进去,从第 PP 个位置开始,一直到原来队尾的所有人,都要往后挪一位

      2. 交换的本质:把“异类”换进去

      现在的局面是:新同学在最后,但他应该在中间。 我们可以把这个过程看作是新同学向左冒泡的过程,或者反过来,看作是把比他高的人一个个向右交换的过程。

      但是,题目允许任意两人交换,不仅仅是相邻交换。这比冒泡排序厉害多了。我们可以直接把新同学和占了他位置的那个人交换。

      模拟过程:

      1. 新同学想去位置 PP,但现在位置 PP 上站着同学 A。
      2. 新同学和同学 A 交换。新同学到了正确位置 PP,搞定!
      3. 现在的麻烦是:同学 A 被换到了队尾,但他比新同学高,应该站在 PP 的后面,比如位置 P+1P+1
      4. 如果位置 P+1P+1 原来的同学 B 身高和同学 A 一样,那 A 站在 P+1P+1 还是队尾,其实没区别(题目说了身高相同顺序任意)。同学 A 可以直接顶替同学 B 的身份,不用再换了。
      5. 但如果位置 P+1P+1 原来的同学 B 身高和同学 A 不一样(比如 B 比 A 高),那 A 站在队尾就不行了。我们需要把 A 换到 P+1P+1 去。于是,A 和 B 交换
      6. 现在 B 到了队尾,我们再给 B 找位置……

      3. 总结规律

      通过上面的模拟,我们发现:

      • 我们只需要把新同学换到正确的位置。
      • 如果被挤出来的那个同学,和后面位置原本的同学身高一样,就不用再换了(混在里面就行)。
      • 只有当身高数值发生变化(比如从 170 变成了 175)时,我们才被迫需要进行一次新的交换,把那个“不同身高”的人归位。

      结论: 最少交换次数 = 1(新同学归位) + 后面受影响的人群中“身高数值变化”的次数


      修改后的 C++ 代码注释

      我将代码中的注释进行了调整,去掉了晦涩的数学术语,改用这种直观的逻辑来解释。

      #include <iostream>
      #include <vector>
      #include <algorithm>
      
      using namespace std;
      
      int main() {
          // 1. 优化输入输出效率
          ios_base::sync_with_stdio(false);
          cin.tie(NULL);
      
          int N;
          if (!(cin >> N)) return 0;
      
          // 读取所有同学的身高
          vector<int> heights(N);
          for (int i = 0; i < N; ++i) {
              cin >> heights[i];
          }
      
          int M;
          if (!(cin >> M)) return 0;
      
          // q 用于维护当前队伍中所有同学的身高,保持有序
          vector<int> q;
          q.reserve(N);
      
          for (int i = 0; i < M; ++i) {
              int x;
              cin >> x;
              int h = heights[x]; // 当前新入队同学的身高
      
              // 核心逻辑:
              // 1. 找到新同学应该站的位置。
              // 使用 upper_bound 查找第一个大于 h 的位置。
              // 这意味着如果有人和新同学一样高,新同学站他们后面。
              // 这样做是为了让新同学尽可能靠后,受影响的人最少。
              auto it = upper_bound(q.begin(), q.end(), h);
              
              int p = distance(q.begin(), it); // 新同学的目标位置下标
              int k = q.size(); // 插入前队伍里的人数
      
              if (p == k) {
                  // 情况 1: 新同学最高(或一样高),直接站在队尾
                  // 队伍依然有序,不需要交换
                  cout << 0 << "\n";
                  q.push_back(h);
              } else {
                  // 情况 2: 新同学比较矮,需要插队到位置 p
                  // 这会导致从位置 p 到队尾(k-1)的所有同学都要往后挪
                  
                  // 第1次交换:把新同学换到位置 p
                  int swaps = 1;
                  
                  // 检查被挤走的同学是否需要继续交换
                  // 只有当后一个位置的身高和前一个位置的身高 *不一样* 时,
                  // 被挤出来的同学才无法“冒充”下一个位置的人,必须再进行一次交换
                  for (int j = p + 1; j < k; ++j) {
                      if (q[j] != q[j-1]) {
                          swaps++;
                      }
                  }
                  cout << swaps << "\n";
                  
                  // 把新同学插入到有序队列中,准备下一轮点名
                  q.insert(it, h);
              }
          }
      
          return 0;
      }
      

      这种解释方式利用了生活中的排队经验,初中生完全可以理解:“如果我被挤到了你的位置,但我俩身高一样,那我就不用动了,直接顶替你;如果不一样,我就得继续换。”

      • 1

      信息

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