Submission #336545

#TimeUsernameProblemLanguageResultExecution timeMemory
336545aanastasovCounting Mushrooms (IOI20_mushrooms)C++17
79.86 / 100
10 ms468 KiB
#include "mushrooms.h" #include <array> #include <algorithm> #include <cassert> #include <vector> #include <iostream> #define DEBUG(x) std::cerr << #x << ": " << x << std::endl /** * Let's say that we want to identify K zeroes or ones in the first phase, * which are subsequently to be used in the second phase, * and let's say that we have B bits of efficiency per small query. * * Then we are going to need (2K - 1) / B + (N - 2K) / K queries in total. * Note that we have N = 20000, so the expression is: (2K - 1) / B + (20000 - 2K) / K. * As per wolframalpha.com, the optimal choice for K is 100 * sqrt(B), * which shows that the optimal K is a function of B, * which does make sense: as we can identify more bits per small query, * it makes sense to identify a larger number of them * so that we need fewer queries in the second phase. * * Finally sqrt(2) = 1.41, sqrt(2.5) = 1.58, and sqrt(3) = 1.73. * With a particular efficiency, we would need * (200 * sqrt(B) - 1) / B + (20000 - 200 * sqrt(B)) / (100 * sqrt(B)). * With 2 bits of effiency, this evaluates to * 281 / 2 + (20000 - 282) / 141 = 280. * * Moreover, if we can get to 3 bits of efficiency per small query, * then we would need (2 * 173) / 3 + (20000 - 2 * 173) / 173 queries = 229 queries, * which is pretty close to what we want. */ const int PHASE_ONE_COUNT = 142; template<typename T> void debugVector(std::vector<T> v) { std::cerr << "{"; for (int i = 0; i < v.size(); ++i) { if (i > 0) std::cerr << ", "; std::cerr << v[i]; } std::cerr << "}" << std::endl; } inline int random(int a, int b) { return a + rand() % (b - a + 1); } struct Machine { int n; std::vector<int> order; Machine(int n) { this->n = n; order = std::vector<int>(n - 1); for (int i = 0; i < order.size(); ++i) { order[i] = i + 1; if (i > 0) { int j = random(0, i - 1); std::swap(order[i], order[j]); } } } int use(std::vector<int> seq) { auto modifiedSeq = std::vector<int>(seq.size(), -1); for (int i = 0; i < seq.size(); ++i) { if (seq[i] == 0) { modifiedSeq[i] = 0; } else { modifiedSeq[i] = order[seq[i] - 1]; } } return use_machine(modifiedSeq); } }; /* int use_my_machine(std::vector<int> seq) { // debugVector(seq); return use_machine(seq); }*/ std::pair<std::vector<int>, std::vector<int>> runPhaseOneTwoAndHalf(int n, int phaseOneCount, Machine &machine) { assert(n >= 1000); // TODO(aanastasov): For this particular algorithm for phase one, // we might need to permute the input to reduce the chances of adversarial input. // We should be able to do this inside use_my_machine, // which will be completely transparent to this implementation, which is pretty nice. auto zeroes = std::vector<int>(); auto ones = std::vector<int>(); zeroes.push_back(0); // Step 1: Identify sufficiently many items to have 3 zeroes or 3 ones. // We spend too many queries here, it's better to utilize the 2-bit efficiency query here. while (zeroes.size() < 3 && ones.size() < 3 && zeroes.size() + ones.size() < n) { int next = zeroes.size() + ones.size(); auto seq = std::vector<int>{0, next}; int count = machine.use(seq); assert(count >= 0 && count <= 1); if (count == 0) zeroes.push_back(next); else ones.push_back(next); } assert(n <= 4 || zeroes.size() == 3 || ones.size() == 3); // Step 2: Utilize a 2.5-bit efficiency query from now on. // The structure is the following: // count = use_machine(known[0], unknown[0], known[1], unknown[1], known[2], unknown[2]), // where known[0] = known[1] = known[2] are all 0s or all 1s, as identified from above. // Now, unknown[2] is completely determined by the parity of count. // // However, there are two cases for unknown[0] and unknown[1]: // Case 1: if unknown[0] = unknown[1], then we know them entirely, so we've identified 3 bits. // Case 2: if unknown[0] /= unknown[1], then we know the are different, but we don't know // which one is which; thus, we attempt to identify unknown[0] with a subsequent query by // designating it as unknown[2], after which we would be able to deduce both. // Therefore, with 50% we identify 3 bits, and with 50% we identify 1 bit + whether // unknown[0] and unknown[1] are same or different, which is 1 more bit, for a total of 2. // In expectation, we have 50% * 3 bits + 50% * 2 bits = 2.5 bits on average. auto prevUnknown = std::vector<int>(); while (zeroes.size() < phaseOneCount && ones.size() < phaseOneCount && zeroes.size() + ones.size() < n) { assert(prevUnknown.empty() || (prevUnknown.size() == 2 && std::min(prevUnknown[0], prevUnknown[1]) > 0)); int next = zeroes.size() + ones.size() + prevUnknown.size(); auto unknown = std::vector<int>{next, next + 1, next + 2}; if (!prevUnknown.empty()) { unknown[2] = prevUnknown[0]; } for (auto x : unknown) if (x >= n) { assert(false); // This shouldn't happen because n >= 1000. } auto known = std::vector<int>(); assert(zeroes.size() >= 3 || ones.size() >= 3); const bool usingZeroes = (zeroes.size() >= 3); if (usingZeroes) { known = std::vector<int>{zeroes[0], zeroes[1], zeroes[2]}; } else { known = std::vector<int>{ones[0], ones[1], ones[2]}; } auto seq = std::vector<int>{known[0], unknown[0], known[1], unknown[1], known[2], unknown[2]}; const int count = machine.use(seq); // Step 2.1: Need to handle the case where prevUnknown.empty() is false, // so that we can identify those two old bits completely; // as of now, we just know that they are not the same. // Step 2.2: If prevUnknown.empty() is true, then we can identify one more item: unknown[2]. if (!prevUnknown.empty()) { if (usingZeroes) { if (count % 2 == 0) { zeroes.push_back(prevUnknown[0]); ones.push_back(prevUnknown[1]); } else { zeroes.push_back(prevUnknown[1]); ones.push_back(prevUnknown[0]); } } else { if (count % 2 == 0) { zeroes.push_back(prevUnknown[1]); ones.push_back(prevUnknown[0]); } else { zeroes.push_back(prevUnknown[0]); ones.push_back(prevUnknown[1]); } } prevUnknown.clear(); } else { if (usingZeroes) { if (count % 2 == 0) zeroes.push_back(unknown[2]); else ones.push_back(unknown[2]); } else { if (count % 2 == 0) ones.push_back(unknown[2]); else zeroes.push_back(unknown[2]); } } assert(prevUnknown.empty()); // Step 2.3: Need to check if unknown[0] and unknown[1] are the same, // in which we case we know them entirely, // or if they are different, in which case, we need to push them to prevUnknown. const int count2 = count % 2 != 0 ? count - 1 : count; assert(count2 == 0 || count2 == 2 || count2 == 4); if (count2 == 0 || count2 == 4) { // same if (usingZeroes) { if (count2 == 0) { zeroes.push_back(unknown[0]); zeroes.push_back(unknown[1]); } else { ones.push_back(unknown[0]); ones.push_back(unknown[1]); } } else { if (count2 == 4) { zeroes.push_back(unknown[0]); zeroes.push_back(unknown[1]); } else { ones.push_back(unknown[0]); ones.push_back(unknown[1]); } } } else { // different prevUnknown.push_back(unknown[0]); prevUnknown.push_back(unknown[1]); } } if (!prevUnknown.empty()) { // Need to handle this case correctly. auto seq = std::vector<int>{0, prevUnknown[0]}; int count = machine.use(seq); if (count == 0) { zeroes.push_back(prevUnknown[0]); ones.push_back(prevUnknown[1]); } else if (count == 1) { zeroes.push_back(prevUnknown[1]); ones.push_back(prevUnknown[0]); } else { assert(false); } } return {zeroes, ones}; } std::pair<std::vector<int>, std::vector<int>> runPhaseOne(int n, int phaseOneCount, Machine &machine) { auto zeroes = std::vector<int>(); auto ones = std::vector<int>(); zeroes.push_back(0); while (zeroes.size() < 2 && ones.size() < 2 && zeroes.size() + ones.size() < n) { int next = zeroes.size() + ones.size(); auto seq = std::vector<int>{0, next}; int count = machine.use(seq); assert(count >= 0 && count <= 1); if (count == 0) zeroes.push_back(next); else ones.push_back(next); } assert(n <= 2 || zeroes.size() == 2 || ones.size() == 2); while (zeroes.size() < phaseOneCount && ones.size() < phaseOneCount && zeroes.size() + ones.size() < n) { int next0 = zeroes.size() + ones.size(); int next1 = next0 + 1; if (next1 >= n) break; bool useKnownZeroes = zeroes.size() >= 2; auto seq = std::vector<int>(); if (useKnownZeroes) seq = std::vector<int>{zeroes[0], next0, zeroes[1], next1}; else seq = std::vector<int>{ones[0], next0, ones[1], next1}; int count = machine.use(seq); assert(count >= 0 && count <= 3); if (useKnownZeroes) { if (count >= 2) ones.push_back(next0); else zeroes.push_back(next0); if (count % 2 != 0) ones.push_back(next1); else zeroes.push_back(next1); } else { if (count < 2) ones.push_back(next0); else zeroes.push_back(next0); if (count % 2 != 1) ones.push_back(next1); else zeroes.push_back(next1); } } return {zeroes, ones}; } int count_mushrooms(int n) { int phaseOneCount = std::min(n / 2, PHASE_ONE_COUNT); // NOTE(aanastasov): This is purely to reduce the number of edge cases // that needs to be consider with the more efficient phase one algorithm. Machine machine(n); auto response = (n <= 1000) ? runPhaseOne(n, phaseOneCount, machine) : runPhaseOneTwoAndHalf(n, phaseOneCount, machine); auto zeroesLocs = response.first; auto onesLocs = response.second; int zeroesCount = 0; int onesCount = 0; int next = zeroesLocs.size() + onesLocs.size(); while (next < n) { int len = std::min(n - next, (int)std::max(zeroesLocs.size(), onesLocs.size())); auto seq = std::vector<int>(2 * len); auto known = zeroesLocs.size() > onesLocs.size() ? zeroesLocs : onesLocs; for (int i = 0; i < len; ++i) seq[i * 2] = known[i]; for (int i = 0; i < len; ++i) seq[i * 2 + 1] = next++; int count = (machine.use(seq) + 1) / 2; if (known == onesLocs) { zeroesCount += count; onesCount += len - count; } else { onesCount += count; zeroesCount += len - count; } } return zeroesCount + zeroesLocs.size(); }

Compilation message (stderr)

mushrooms.cpp: In constructor 'Machine::Machine(int)':
mushrooms.cpp:56:27: warning: comparison of integer expressions of different signedness: 'int' and 'std::vector<int>::size_type' {aka 'long unsigned int'} [-Wsign-compare]
   56 |         for (int i = 0; i < order.size(); ++i) {
      |                         ~~^~~~~~~~~~~~~~
mushrooms.cpp: In member function 'int Machine::use(std::vector<int>)':
mushrooms.cpp:67:27: warning: comparison of integer expressions of different signedness: 'int' and 'std::vector<int>::size_type' {aka 'long unsigned int'} [-Wsign-compare]
   67 |         for (int i = 0; i < seq.size(); ++i) {
      |                         ~~^~~~~~~~~~~~
mushrooms.cpp: In function 'std::pair<std::vector<int>, std::vector<int> > runPhaseOneTwoAndHalf(int, int, Machine&)':
mushrooms.cpp:96:80: warning: comparison of integer expressions of different signedness: 'std::vector<int>::size_type' {aka 'long unsigned int'} and 'int' [-Wsign-compare]
   96 |     while (zeroes.size() < 3 && ones.size() < 3 && zeroes.size() + ones.size() < n) {
      |                                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~
mushrooms.cpp:122:26: warning: comparison of integer expressions of different signedness: 'std::vector<int>::size_type' {aka 'long unsigned int'} and 'int' [-Wsign-compare]
  122 |     while (zeroes.size() < phaseOneCount
      |            ~~~~~~~~~~~~~~^~~~~~~~~~~~~~~
mushrooms.cpp:123:28: warning: comparison of integer expressions of different signedness: 'std::vector<int>::size_type' {aka 'long unsigned int'} and 'int' [-Wsign-compare]
  123 |             && ones.size() < phaseOneCount
      |                ~~~~~~~~~~~~^~~~~~~~~~~~~~~
mushrooms.cpp:124:44: warning: comparison of integer expressions of different signedness: 'std::vector<int>::size_type' {aka 'long unsigned int'} and 'int' [-Wsign-compare]
  124 |             && zeroes.size() + ones.size() < n) {
      |                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~
mushrooms.cpp: In function 'std::pair<std::vector<int>, std::vector<int> > runPhaseOne(int, int, Machine&)':
mushrooms.cpp:213:80: warning: comparison of integer expressions of different signedness: 'std::vector<int>::size_type' {aka 'long unsigned int'} and 'int' [-Wsign-compare]
  213 |     while (zeroes.size() < 2 && ones.size() < 2 && zeroes.size() + ones.size() < n) {
      |                                                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~
mushrooms.cpp:224:26: warning: comparison of integer expressions of different signedness: 'std::vector<int>::size_type' {aka 'long unsigned int'} and 'int' [-Wsign-compare]
  224 |     while (zeroes.size() < phaseOneCount && ones.size() < phaseOneCount && zeroes.size() + ones.size() < n) {
      |            ~~~~~~~~~~~~~~^~~~~~~~~~~~~~~
mushrooms.cpp:224:57: warning: comparison of integer expressions of different signedness: 'std::vector<int>::size_type' {aka 'long unsigned int'} and 'int' [-Wsign-compare]
  224 |     while (zeroes.size() < phaseOneCount && ones.size() < phaseOneCount && zeroes.size() + ones.size() < n) {
      |                                             ~~~~~~~~~~~~^~~~~~~~~~~~~~~
mushrooms.cpp:224:104: warning: comparison of integer expressions of different signedness: 'std::vector<int>::size_type' {aka 'long unsigned int'} and 'int' [-Wsign-compare]
  224 |     while (zeroes.size() < phaseOneCount && ones.size() < phaseOneCount && zeroes.size() + ones.size() < n) {
      |                                                                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~
#Verdict Execution timeMemoryGrader output
Fetching results...