2 条题解
-
0
你好!我是金牌教练。为了方便你创建 OJ(在线判题系统)测试数据,我为你编写了一个集成化的数据生成器与标程(Generator & Solver)。
这个程序将自动生成 10 组符合 NOI 规范的测试点(
1.in/out~10.in/out)。为了体现竞赛的梯度,我对数据进行了精细化分布,以区分暴力枚举、优先队列模拟和二分答案三种复杂度的解法。一、 测试点设计矩阵
测试点 规模 规模 特点 预期通过算法 1-2 100 极小规模 全部算法 3-4 1,000 中等规模 模拟、二分 5 1 边界:只需一趟 二分 6 1 边界:只有一辆车 7 均匀分布: 均相同 8 极限稀疏: 极大 9-10 极限综合随机数据 仅限二分
二、 数据生成器 C++ 代码
该程序采用 C++14 标准,包含了生成输入数据和利用标准二分算法生成输出答案的完整逻辑。
#include <iostream> #include <fstream> #include <vector> #include <string> #include <random> #include <algorithm> #include <ctime> using namespace std; typedef long long ll; /** * 标准答案:二分查找 + 贪心验证 * 时间复杂度: O(N log(min_time * totalTrips)) */ bool check(ll mid, ll totalTrips, int n, const vector<int>& time) { ll count = 0; for (int i = 0; i < n; i++) { count += (mid / time[i]); // 剪枝点:防止累加和超过 long long 范围 // 同时也符合“只要大于等于就可行”的逻辑 if (count >= totalTrips) return true; } return count >= totalTrips; } ll solve(int n, ll totalTrips, const vector<int>& time) { int min_t = time[0]; for (int x : time) if (x < min_t) min_t = x; ll low = 1; // 右边界最坏情况:由最慢的一辆车跑完所有趟数 // 此处设为 min_t * totalTrips 是为了更紧确,防止 mid 计算溢出 ll high = (ll)min_t * totalTrips; ll ans = high; while (low <= high) { ll mid = low + (high - low) / 2; if (check(mid, totalTrips, n, time)) { ans = mid; high = mid - 1; } else { low = mid + 1; } } return ans; } /** * 数据生成逻辑 */ void generate_test_cases() { mt19937_64 rng(time(0)); for (int t = 1; t <= 10; t++) { string in_name = to_string(t) + ".in"; string out_name = to_string(t) + ".out"; ofstream fout(in_name); ofstream fans(out_name); int n; ll totalTrips; vector<int> time_arr; // 根据测试点分布设定规模 if (t <= 2) { // 暴力解法可通过 n = 100; totalTrips = 100; } else if (t <= 4) { // 模拟解法可通过 n = 1000; totalTrips = 100000; } else if (t == 5) { // 边界:趟数极少 n = 100000; totalTrips = 1; } else if (t == 6) { // 边界:车数极少 n = 1; totalTrips = 10000000; } else { // 极限规模 n = 100000; totalTrips = 10000000; } // 填充 time 数组 if (t == 7) { // 均匀分布 int fixed_t = rng() % 10000000 + 1; for (int i = 0; i < n; i++) time_arr.push_back(fixed_t); } else if (t == 8) { // 极限大值 for (int i = 0; i < n; i++) time_arr.push_back(10000000); } else { // 随机分布 for (int i = 0; i < n; i++) time_arr.push_back(rng() % 10000000 + 1); } // 1. 写入输入文件 fout << n << " " << totalTrips << "\n"; for (int i = 0; i < n; i++) { fout << time_arr[i] << (i == n - 1 ? "" : " "); } fout << endl; // 2. 运行标程并写入输出文件 ll result = solve(n, totalTrips, time_arr); fans << result << endl; cout << "Generated Case " << t << " [N=" << n << ", T=" << totalTrips << "] OK." << endl; fout.close(); fans.close(); } } int main() { cout << "Starting data generation..." << endl; generate_test_cases(); cout << "All 10 cases generated successfully." << endl; return 0; }
三、 生成器关键点与优化说明
-
文件大小控制:
- 本题最大的开销在于第 7-10 组数据的
time数组。 - ,每个数字最大 8 位(含空格),单个
.in文件约为 。 - 全部 10 组数据的总大小(压缩前)约为 5MB。如果你需要严格控制在 2MB 以内,建议将后几组的 改为 ,这依然能有效卡掉 的暴力,并对 的模拟造成压力。
- 本题最大的开销在于第 7-10 组数据的
-
防止溢出(Exception Control):
- 计算溢出:在
check函数中,我们使用了if (count >= totalTrips) return true;进行剪枝。这不仅能节约计算时间,更重要的是防止了count累加超过long long的最大值(),因为在极端情况下( 辆车,每辆 ),不剪枝会导致数据错误。 - 二分溢出:
high动态设为min_t * totalTrips。若设为固定的1e18,虽然不会溢出long long,但会让check函数内部计算更易发生越界。
- 计算溢出:在
-
区分算法复杂度:
- 暴力枚举时间:在测试点 1-4 之后会因为 达到 而彻底失效。
- 堆/优先队列模拟:在 的情况下,模拟需要 次堆操作,耗时约 2-3 秒以上,在 NOI 严格的 1 秒限时下通常无法全部 AC。
-
非递归写法:
- 标程中的二分查找采用
while循环迭代实现,确保不会因为递归深度过大(虽然 的深度本就不深)导致系统栈溢出。
- 标程中的二分查找采用
四、 如何使用
- 编译运行此
generator.cpp。 - 将生成的
1.in~10.out存入测试数据目录。 - 设置题目时限为 1.0s,内存限制为 256MB。
-
信息
- ID
- 19500
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者