1 条题解

  • 0
    @ 2025-12-17 18:27:33

    这是一个完整的 C++ 数据生成器。它会生成 10 组测试数据(1.in/1.out ~ 10.in/10.out),涵盖了从极小数据、正交向量、成比例向量、精度测试到最大数据规模的各类情况。

    数据生成器设计思路

    1. Solver 类:内置了一个标准的 AC 解法,用于根据生成的 .in 文件计算正确的 .out 答案。解法中使用 double 进行计算,遵循“严格大于则更新”的原则来处理 tie-breaking(相同最大值取最小编号)。
    2. 生成策略
      • Case 1: 极小规模 (N=1,M=1N=1, M=1),测试基本连通性。
      • Case 2: 小规模随机,方便肉眼检查。
      • Case 3: 正交测试。目标向量与某些候选向量垂直(点积为0,相似度0),测试零值处理。
      • Case 4: 完美缩放。候选向量包含目标向量的整数倍(如 2A,3A2A, 3A),测试相似度为 1.0 的情况。
      • Case 5: 平局测试(Tie-breaking)。设计多个候选向量与目标向量相似度完全相同,验证是否输出了最小编号。
      • Case 6: 稀疏向量。包含大量 0,测试分母计算和 0 的处理。
      • Case 7: 极近向量。候选向量之间差异极小,测试浮点数精度。
      • Case 8: 全同向量。所有候选向量都一样。
      • Case 9: 最大值边界。所有元素都是 100,测试数值溢出(虽然这题 int 够用,但验证逻辑)。
      • Case 10: 最大规模随机 (N=100,M=100N=100, M=100)。

    完整代码 (generator.cpp)

    #include <iostream>
    #include <fstream>
    #include <vector>
    #include <cmath>
    #include <algorithm>
    #include <random>
    #include <ctime>
    #include <string>
    #include <iomanip>
    
    using namespace std;
    
    // ==========================================
    // Part 1: 标准 AC 解法 (Solver)
    // ==========================================
    class Solver {
    public:
        int solve(int n, int m, const vector<int>& A, const vector<vector<int>>& B) {
            double maxSim = -2.0; // 余弦相似度最小是 -1,设 -2 安全
            int ansIdx = 1;
    
            // 计算 A 的模长
            double normA = 0;
            for (int x : A) normA += (long long)x * x;
            normA = sqrt(normA);
    
            for (int k = 0; k < m; ++k) {
                double dot = 0;
                double normB = 0;
                for (int i = 0; i < n; ++i) {
                    dot += (long long)A[i] * B[k][i];
                    normB += (long long)B[k][i] * B[k][i];
                }
                normB = sqrt(normB);
                
                // 题目保证模长不为0
                double sim = dot / (normA * normB);
                
                // 题目要求:相似度最大;若相同,取编号最小。
                // 因为我们是按编号 1..m 顺序遍历,只有 sim "严格大于" maxSim 时才更新
                // 这样如果是 "等于",就不会更新,保留了之前的(较小的)编号
                if (sim > maxSim) {
                    maxSim = sim;
                    ansIdx = k + 1;
                }
            }
            return ansIdx;
        }
    };
    
    // ==========================================
    // Part 2: 数据生成工具箱
    // ==========================================
    mt19937 rng(time(0));
    
    // 确保向量不全为 0 (题目约束)
    void ensure_nonzero(vector<int>& v) {
        bool all_zeros = true;
        for(int x : v) if(x != 0) all_zeros = false;
        if (all_zeros) {
            uniform_int_distribution<int> idx_dist(0, v.size() - 1);
            uniform_int_distribution<int> val_dist(1, 100);
            v[idx_dist(rng)] = val_dist(rng);
        }
    }
    
    // 生成随机向量
    vector<int> gen_vector(int n, int min_v, int max_v, double zero_prob = 0.0) {
        vector<int> v(n);
        uniform_int_distribution<int> val_dist(min_v, max_v);
        uniform_real_distribution<double> prob_dist(0.0, 1.0);
        
        for(int i=0; i<n; ++i) {
            if (prob_dist(rng) < zero_prob) v[i] = 0;
            else v[i] = val_dist(rng);
        }
        ensure_nonzero(v);
        return v;
    }
    
    // ==========================================
    // Part 3: 主程序
    // ==========================================
    int main() {
        Solver solver;
        cout << "Generating test data for Cosine Similarity..." << endl;
    
        for (int i = 1; i <= 10; ++i) {
            string in_file = to_string(i) + ".in";
            string out_file = to_string(i) + ".out";
            
            ofstream fin(in_file);
            ofstream fout(out_file);
            
            int N, M;
            vector<int> A;
            vector<vector<int>> B;
            string desc;
    
            // ---- 生成策略 ----
            switch(i) {
                case 1: 
                    // 极小值边界
                    N = 1; M = 1;
                    A = {10};
                    B = {{5}};
                    desc = "Min constraints N=1, M=1";
                    break;
                case 2: 
                    // 小规模随机
                    N = 5; M = 5;
                    A = gen_vector(N, 0, 10);
                    for(int k=0; k<M; ++k) B.push_back(gen_vector(N, 0, 10));
                    desc = "Small Random";
                    break;
                case 3: 
                    // 正交测试 (dot product = 0)
                    N = 2; M = 3;
                    A = {1, 0}; 
                    // B1: 垂直 (0, 10), B2: 平行 (10, 0), B3: 45度 (1, 1)
                    B = {{0, 10}, {10, 0}, {1, 1}};
                    desc = "Orthogonal and Parallel vectors";
                    break;
                case 4: 
                    // 缩放测试 (Sim = 1.0)
                    N = 5; M = 4;
                    A = {1, 2, 3, 4, 5};
                    B.push_back({2, 4, 6, 8, 10}); // 2倍,Sim=1.0
                    B.push_back({5, 1, 1, 1, 1});  // 杂乱
                    B.push_back({10, 20, 30, 40, 50}); // 10倍,Sim=1.0
                    B.push_back({0, 0, 0, 0, 1});
                    desc = "Scaled vectors (Sim=1.0)";
                    break;
                case 5: 
                    // 平局测试 (Tie Breaking)
                    // 构造 B1 和 B3 相似度相同且最大,应输出 1
                    N = 2; M = 4;
                    A = {1, 1};
                    B.push_back({2, 2}); // Sim = 1.0, ID=1
                    B.push_back({1, 0});
                    B.push_back({5, 5}); // Sim = 1.0, ID=3
                    B.push_back({0, 1});
                    desc = "Tie breaking (Output smallest ID)";
                    break;
                case 6: 
                    // 稀疏向量
                    N = 20; M = 20;
                    A = gen_vector(N, 1, 10, 0.7); // 70% 概率为0
                    for(int k=0; k<M; ++k) B.push_back(gen_vector(N, 1, 10, 0.7));
                    desc = "Sparse vectors (Many zeros)";
                    break;
                case 7: 
                    // 精度测试 (Very close vectors)
                    N = 3; M = 3;
                    A = {100, 100, 100};
                    B.push_back({100, 100, 99}); // Slightly off
                    B.push_back({100, 100, 100}); // Perfect
                    B.push_back({99, 100, 100}); // Slightly off
                    desc = "Precision test";
                    break;
                case 8: 
                    // 边界数值 (All 100)
                    N = 10; M = 10;
                    A = vector<int>(N, 100);
                    for(int k=0; k<M; ++k) B.push_back(vector<int>(N, 100));
                    // 修改最后一个稍微不同
                    B.back()[0] = 1; 
                    desc = "Max values (All 100)";
                    break;
                case 9: 
                    // 一般随机
                    N = 50; M = 50;
                    A = gen_vector(N, 0, 100);
                    for(int k=0; k<M; ++k) B.push_back(gen_vector(N, 0, 100));
                    desc = "Medium Random";
                    break;
                case 10:
                    // 最大规模
                    N = 100; M = 100;
                    A = gen_vector(N, 0, 100);
                    for(int k=0; k<M; ++k) B.push_back(gen_vector(N, 0, 100));
                    desc = "Max Constraints N=100, M=100";
                    break;
            }
    
            // ---- 写入输入文件 ----
            fin << N << " " << M << endl;
            for(int k=0; k<N; ++k) fin << A[k] << (k==N-1 ? "" : " ");
            fin << endl;
            
            for(int i=0; i<M; ++i) {
                for(int k=0; k<N; ++k) {
                    fin << B[i][k] << (k==N-1 ? "" : " ");
                }
                fin << endl;
            }
    
            // ---- 计算并写入输出文件 ----
            int ans = solver.solve(N, M, A, B);
            fout << ans << endl;
    
            fin.close();
            fout.close();
            cout << "Generated Case " << i << ": " << desc << endl;
        }
        
        cout << "\nAll test cases generated successfully." << endl;
        return 0;
    }
    

    使用方法

    1. 将代码保存为 generator.cpp
    2. 编译:g++ generator.cpp -o generator
    3. 运行:./generator(Windows 下是 generator.exe)。
    4. 程序运行结束后,当前目录下会生成 1.in/1.out10.in/10.out

    数据说明

    • 格式:完全符合 GESP 4级和 OI 题目格式。
    • 约束:确保了所有向量模长不为 0(ensure_nonzero 函数),数值在 [0, 100] 之间。
    • 边界覆盖
      • Tie-breaking(Case 5):这是此题最容易错的地方,生成器构造了两个完全一样的最大相似度,标准答案会输出较小的 ID,用来卡掉没有处理平局情况的代码。
      • Scaled(Case 4):测试学生是否理解向量方向相同即相似度为 1。
      • Max N/M(Case 10):测试程序的数组大小和计算效率。
    • 1

    信息

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