// Usage: ./validator tc.in tc.ans dir out #include #include #include #include #include #include #include #include using namespace std; const int POPULATION = 10'000'000; const int MIN_K = 1; const int MAX_K = POPULATION; const int MAX_ROUNDS = 50; const char* out_dir = nullptr; void judge_message(const string& msg) { cerr << msg << endl; if (out_dir) { string fname = out_dir + string("/judgemessage.txt"); ofstream fout(fname.c_str()); fout << msg << endl; } } [[noreturn]] void reject(const string& msg) { judge_message(msg); exit(43); } [[noreturn]] void accept(const string& msg) { judge_message(msg); exit(42); } long double lfact(int arg) { return lgammal(1.0L * (arg + 1)); } int main(int argc, char** argv) { assert(argc >= 2); if (argc >= 4) out_dir = argv[3]; ifstream fin(argv[1]); int ans; long long seed; fin >> ans >> seed; int healthy = POPULATION - ans; assert(fin); string dummy; assert(!(fin >> dummy)); std::mt19937_64 rng(seed); // make the randomness static within a test case std::uniform_real_distribution<> distr(0.0, 1.0); for (int i = 0; i <= MAX_ROUNDS; i++) { string cmd; if (!(cin >> cmd)) reject("failed to read cmd"); transform(cmd.begin(), cmd.end(), cmd.begin(), ::tolower); if (cmd == "estimate") break; if (cmd != "test") reject("invalid command"); if (i == MAX_ROUNDS) reject("too many test rounds"); int sample; if (!(cin >> sample)) reject("failed to read test size"); if (sample < MIN_K || sample > MAX_K) reject("sample is out of bounds"); // prob. that 'sample' out of 'POPULATION' people are all healthy long double prob; if (sample > healthy) { prob = 0; } else { // (healthy choose sample) / (POPULATION choose sample) // = healthy! / POPULATION! / (healthy - sample)! * (POPULATION - sample)! prob = exp( lfact(healthy) - lfact(healthy - sample) - lfact(POPULATION) + lfact(POPULATION - sample)); // Numerical precision: // Each lfact (lgamma) call has error of a handful of ulp, so say // 2e-19 * lfact(10^7) ~ 1e-10 in absolute error. Each partial sum // has a mathematical result smaller than lfact(10^7) in absolute // value, so the intermediate absolute errors are similarly small, // say 1e-10. This yields a total absolute error of less than 1e-9. // // Applying exp will turn the absolute error into relative error: // e^(a + eps) = e^a * e^eps ~ e^a * (1 + eps) // // A relative error on the probability of 1e-9 is pretty fine in // our case, though for increased safety, let's always answer "0" // if the probability is near 1. (Near 0 we have no problems.) if (prob > 1 - 1e-9) { prob = 1; } } // cycle through a few numbers of the RNG depending on the // sample size to remove monotonicity and statelessness of // query results int skip_period = 1 + rng() % 1000; int skips = sample % skip_period; for (int i = 0; i < skips; ++i) rng(); bool result = (distr(rng) < prob); cout << (result ? "0" : "1") << endl; } int guess; if (!(cin >> guess)) reject("failed to read answer"); if (cin >> dummy) reject("expected eof"); ostringstream oss; oss << "guessed " << guess << ", was " << ans; oss << " (rel. error " << (guess / (double)ans - 1) * 100 << "%)"; string msg = oss.str(); // (the first comparison here is to avoid overflows) if (0 <= guess && guess <= ans * 2 && ans <= guess * 2) { accept(msg); } else { reject(msg); } }