出题不背锅

以洛谷 GESP202503 八级 割裂为例

第一步,生成题面

建议使用markdown语法生成题面,markdown对 LATEX\color{#45ef34}{LATEX} 公式支持不错。

题面主要包括以下内容:

  1. 题目背景(有无都可)
  2. 题目描述
  3. 输入格式
  4. 输出格式
  5. 样例
  6. 数据规模 每一项建议用二级标题

样例可以使用代码块,比如

5
1 2 3 4 5

第二步, 生成STD

代码略,一定要对拍,确保std不出错,我习惯用a.cc来命名正解代码。

第三步,数据生成器(generator)

根据题目要求生成对应格式的数据,我习惯用 data.cc 来命名,数据生成器和后面的SPJ、validator等都建议使用 testlib.h 来做

#include <bits/stdc++.h>

#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <iostream>

#include "testlib.h"
using namespace std;
const int N = 1E3;
int t;
int n = 1e6, a = 1e5;

void makeTree()
{
    vector<int> tr;
    tr.emplace_back(1);
    for (int i = 2; i <= n; i++)
    {
        int idx = rnd.next(0, (int)tr.size() - 1);
        cout << tr[idx] << " " << i << endl;
        tr.emplace_back(i);
    }
}
int main(int argc, char* argv[])
{
    if (argc > 1) t = atoi(argv[1]);
    registerGen(argc, argv, 1);
    //使用参数 t 来规定是第几个测评数据,然后根据t来生成不同大小或范围的数据
    if (t <= 2)
        n = 10, a = 0;
    else if (t <= 3)
        n = rnd.next(20, 100), a = rnd.next(20, 100);
    else if (t <= 4)
        n = 100, a = 100;
    else if (t <= 9)
        n = rnd.next(10000, n), a = rnd.next(1000, (int)min((long long)(n - 1) * n / 2, (long long)a));
    cout << n << " " << a << endl;
    // 生成树
    makeTree();
    //好点对
    map<pair<int, int>, int> mp;
    for (int i = 1; i <= a; i++)
    {
        int u = rnd.next(1, n), v = rnd.next(1, n);
        while (u == v || mp.count(make_pair(u, v)) )
            u = rnd.next(1, n), v = rnd.next(1, n);
        mp[make_pair(u, v)]  = 1;
        cout << u << " " << v << endl;
    }
    // 坏节点对
    int u = rnd.next(1, n), v = rnd.next(1, n);
    while (u == v) u = rnd.next(1, n), v = rnd.next(1, n);
    cout << u << " " << v << endl;

    return 0;
}


第四步,数据校验器(validator)

用来验证生成的测评输入数据是否正确,这一点很重要,尤其是字符串有关的题目要注意 windows 和 linux 下 换行的不同。

#include <bits/stdc++.h>

#include "testlib.h"

using namespace std;
vector<int> fa;
int find(int x) { return (fa[x] == x ? x : fa[x] = find(fa[x])); }
bool isTree(vector<pair<int, int>> &tr, int n)
{
    // n-1条边
    if (n - 1 != tr.size()) return 0;
    fa.resize(n + 1);
    for (int i = 1; i <= n; i++) fa[i] = i;
    // 是不是有环
    for (auto &e : tr)
    {
        int ra = find(e.first);
        int rb = find(e.second);
        if (ra == rb)  // 有环
            return 0;
        fa[ra] = rb;
    }
    // 是不是连通
    int root = find(1);
    for (int i = 1; i <= n; i++)
    {
        int ra = find(i);
        if (root != ra) return 0;
    }
    return 1;
}
int main(int argc, char *argv[])
{
    registerValidation(argc, argv);
    int n = inf.readInt(1, (int)1e6, "n");
    inf.readSpace();
    int a = inf.readInt(0, (int)1e5, "a");
    inf.readEoln();
    vector<pair<int, int>> tr;
    for (int i = 1; i < n; i++)
    {
        int u = inf.readInt(1, n, "u");
        inf.readSpace();
        int v = inf.readInt(1, n, "v");
        tr.emplace_back(make_pair(u, v));
        inf.readEoln();
    }
    ensuref(isTree(tr, n), "not a tree!");
    for (int i = 1; i <= a; i++)
    {
        int x = inf.readInt(1, n, "x");
        inf.readSpace();
        int y = inf.readInt(1, n, "y");
        ensuref(x != y, "not eq!");
        inf.readEoln();
    }

    int x = inf.readInt(1, n, "b_u");
    inf.readSpace();
    int y = inf.readInt(1, n, "b_v");
    ensuref(x != y, "not eq!");
    inf.readEoln();
    // string n = inf.readToken("0|[1-9][0-9]{0,9}", "n");
    // ensure(n.find("0") != string::npos);
    // ensuref(1 == 2, "%d == %d error", 1, 2);
    //
    // inf.readSpace();
    // inf.readInt(1, 9, "k");
    // inf.readEoln();

    // InStream nStream(inf, n);
    // nStream.readInt(0, 2000000000, "n");

    inf.readEof();
}

第五步,生成测评数据

如果中间有错误,及时返回前面的步骤进行修改。

#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

char f[200];
int num = 10;
int main(int argc, char *argv[])
{
    // 参数1为生成测评点的数量
    if (argc > 1) num = atoi(argv[1]);
    if (system("g++ -std=c++17 a.cc -o a"))
    {
        cout << "compile std program error, exit!" << endl;
        return 0;
    }  // std标程
    if (system("g++ -std=c++17 data.cc -o data"))  // gen生成数据
    {
        cout << "Compile data generator error, exit!" << endl;
        return 0;
    }
    if (system("g++ -std=c++17 validator.cc -o v"))  // vallidator 数据校验器
    {
        cout << "Compile validator error, exit!" << endl;
        return 0;
    }
    double st, en, ts, te;
    for (int i = 1; i <= num; i++)
    {
        ts = clock();
        snprintf(f, sizeof f, "./data %d > %02d.in", i, i);
        st = clock();
        if (system(f))
        {
            cout << f << ", make data error, exit" << endl;
            return 0;
        }
        en = clock();
        // 数据校验
        snprintf(f, sizeof f, "./v < %02d.in", i);
        if (system(f))
        {
            cout << "data validator error, exit!" << endl;
            return 0;
        }
        cout << i << ": make data need " << en - st << "ms,";

        snprintf(f, sizeof f, "./a < %02d.in > %02d.ans", i, i);
        st = clock();
        system(f);
        en = clock();
        te = clock();
        cout << "make ans need " << en - st << "ms, total:" << te - ts << "ms, "
             << " OK!" << endl;
    }
    return 0;
}

第六步,验题

这一步也是不出锅的重要的一步,让别人来验一验你出的题,可能有你想不到的错误存在。