2 条题解
-
0
这是一个可以生成 10 组测试数据(
.in和.out文件)的 C++ 程序。它包含了数据生成逻辑和标准解法。功能特点
- 覆盖全面:包含样例、小规模数据、大规模互不相同数据、大规模重复数据、升序/降序特殊情况、所有身高相同等边界情况。
- 自动求解:内置了针对本题的最优解算法,自动生成对应的
.out答案文件,确保数据正确性。 - 文件生成:直接在当前目录下生成
1.in~10.in和1.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; }使用说明
- 将上述代码保存为
generator.cpp。 - 使用 C++ 编译器编译运行(例如
g++ generator.cpp -o gen && ./gen)。 - 程序运行结束后,当前目录下会出现
1.in~10.in以及对应的1.out~10.out。
测试点覆盖详情
- Case 1-2: 题目给定的样例,用于验证基础逻辑。
- Case 3-5: 针对 的数据限制(互不相同)。
- Case 3: 随机互不相同。
- Case 4: 已排序的输入(可能触发某些排序算法的最优/最差情况)。
- Case 5: 逆序输入。
- Case 6-10: 针对 的数据限制(允许重复)。
- Case 6: 极端情况,所有身高完全相同(应输出全 0)。
- Case 7: 只有两种身高,测试对重复段的处理。
- Case 8: 密集的小范围随机数。
- Case 9: 离散的重复数。
- Case 10: 满数据范围的随机大数。
-
0
我们可以用 更直观、更符合直觉的“空位填补”和“冒泡排序” 的思想来解释这个最少交换次数的问题。
符合中学知识范畴的解释
1. 问题转化:排好队的插队问题
想象现在的队伍已经按照身高从矮到高排好了(就像做操排队一样)。 这时候,新同学(身高为 )来了,他一开始站在队伍的最后面。
-
情况一:他比队尾的人还高(或一样高)。 那他直接站在最后就行了,队伍依然是有序的。 交换次数:0。
-
情况二:他比较矮,需要插队到中间某个位置。 假设正确的位置应该是第 个位置(下标从 0 开始)。 为了让他站进去,从第 个位置开始,一直到原来队尾的所有人,都要往后挪一位。
2. 交换的本质:把“异类”换进去
现在的局面是:新同学在最后,但他应该在中间。 我们可以把这个过程看作是新同学向左冒泡的过程,或者反过来,看作是把比他高的人一个个向右交换的过程。
但是,题目允许任意两人交换,不仅仅是相邻交换。这比冒泡排序厉害多了。我们可以直接把新同学和占了他位置的那个人交换。
模拟过程:
- 新同学想去位置 ,但现在位置 上站着同学 A。
- 新同学和同学 A 交换。新同学到了正确位置 ,搞定!
- 现在的麻烦是:同学 A 被换到了队尾,但他比新同学高,应该站在 的后面,比如位置 。
- 如果位置 原来的同学 B 身高和同学 A 一样,那 A 站在 还是队尾,其实没区别(题目说了身高相同顺序任意)。同学 A 可以直接顶替同学 B 的身份,不用再换了。
- 但如果位置 原来的同学 B 身高和同学 A 不一样(比如 B 比 A 高),那 A 站在队尾就不行了。我们需要把 A 换到 去。于是,A 和 B 交换。
- 现在 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
- 上传者