Кодерун путешествует по архипелагу и собирает ре...

Creato il: 16 luglio 2026

Risposto usando GPT-5.6 Thinking da Chat01

Domanda

Кодерун путешествует по архипелагу и собирает редкие штампы в «Паспорт морского путешественника».

Каждый штамп можно получить на конкретном острове. У штампа есть редкость rarity. Некоторые штампы можно получить только после получения других, то есть у штампа может быть не более одного непосредственного требования, а все зависимости образуют лес без циклов.

Не все острова доступны: между ними есть ориентированные переправы, и Кодерун может получать штампы только на островах, достижимых из стартового острова.

Организаторы подготовили API-сервер с данными об островах, переправах и штампах. Ваша программа должна обращаться к этому серверу и выбирать набор штампов с максимальной суммарной редкостью.
Что считается корректным

Для каждого штампа известны:

text
stamp_id — идентификатор штампа; island_id — идентификатор острова; rarity — редкость штампа; requires — список требований.

В этой задаче гарантируется, что:

text
requires содержит не более одного штампа; граф зависимостей штампов не содержит циклов.

Набор и порядок получения штампов считаются корректными, если:

text
каждый выбранный штамп расположен на острове, достижимом из стартового острова; для каждого выбранного штампа все его зависимости тоже выбраны; каждый штамп из requires появляется в выводе раньше самого штампа; общее число выбранных штампов не превосходит k.

Что нужно максимизировать

Если выбрано множество штампов S, то

score = sum(rarity) по всем штампам из S.

Требуется вывести любой корректный порядок получения не более k штампов, для которого score максимален.

Если существует несколько оптимальных ответов, допускается вывести любой.
Формат ввода

На стандартный ввод вашей программы подаются:

text
в первой строке base_url локального API-сервера; во второй строке tour_id — идентификатор сценария.

Ограничения:

text
base_url — корректный URL вида http://127.0.0.1:<port>; tour_id — целое число; в тестах 8 <= number_of_islands <= 90; в тестах 10 <= number_of_stamps <= 420; 1 <= max_stamps <= 90; для каждого штампа 1 <= rarity <= 100, а список requires содержит не более одного идентификатора.

Формат вывода

Если невыгодно или невозможно получить ни одного штампа, выведите:

0

Иначе выведите:

text
в первой строке число p — количество выбранных штампов; во второй строке p различных идентификаторов штампов в порядке получения.

Пример:
Данные из потока ввода

http://127.0.0.1:8080
42

Данные сервера
Ответ GET /tour/42:

{
"tour_id": "42",
"start_island": 1,
"max_stamps": 4
}

Ответ GET /islands?tour_id=42:

{
"tour_id": "42",
"islands": [1, 2, 3, 4, 5]
}

Ответ GET /routes?tour_id=42:

{
"tour_id": "42",
"routes": [
{ "from": 1, "to": 2 },
{ "from": 2, "to": 3 },
{ "from": 1, "to": 4 },
{ "from": 4, "to": 5 }
]
}

Ответ GET /stamps?tour_id=42:

{
"tour_id": "42",
"stamps": [
{
"stamp_id": 3,
"island_id": 2,
"rarity": 7,
"requires": []
},
{
"stamp_id": 8,
"island_id": 3,
"rarity": 10,
"requires": [3]
},
{
"stamp_id": 11,
"island_id": 1,
"rarity": 5,
"requires": []
},
{
"stamp_id": 15,
"island_id": 5,
"rarity": 12,
"requires": [11]
}
]
}

Вывод

4
11 3 8 15

Примечание

text
Сервер доступен только во время текущего запуска программы. Проверяющая система не требует буквального совпадения с эталонным ответом: принимается любой оптимальный ответ.

Документация API

Разбор примера

Ограничения

Ограничение времени
5 с
Ограничение памяти
512 МБ

Примеры
Пример 1
Ввод

{
"public": {
"tour_id": 42,
"stdin_lines": [
42
]
},
"private": {
"server": {
"tour_id": 42,
"start_island": 1,
"max_stamps": 4,
"islands": [
1,
2,
3,
4,
5
],
"routes": [
{
"from": 1,
"to": 2
},
{
"from": 2,
"to": 3
},
{
"from": 1,
"to": 4
},
{
"from": 4,
"to": 5
}
],
"stamps": [
{
"stamp_id": 3,
"island_id": 2,
"rarity": 7,
"requires": []
},
{
"stamp_id": 8,
"island_id": 3,
"rarity": 10,
"requires": [
3
]
},
{
"stamp_id": 11,
"island_id": 1,
"rarity": 5,
"requires": []
},
{
"stamp_id": 15,
"island_id": 5,
"rarity": 12,
"requires": [
11
]
},
{
"stamp_id": 21,
"island_id": 4,
"rarity": 6,
"requires": []
},
{
"stamp_id": 29,
"island_id": 5,
"rarity": 4,
"requires": [
21
]
}
]
}
}
}

Вывод

4
3 8 11 15

#include <algorithm>
#include <cctype>
#include <iostream>
#include <queue>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

#include <curl/curl.h>
#include "json.hpp"

using json = nlohmann::json;
using int64 = long long;

static constexpr int NEG = -1'000'000'000;

static int K;

struct StampNode {
int64 id = 0;
int rarity = 0;

text
std::vector<int> children; // dp[x] — максимальная редкость при выборе x штампов // из поддерева. Если x > 0, текущий штамп обязательно выбран. std::vector<int> dp; // prefix[i] — результат объединения детей с индексами [0, i). std::vector<std::vector<int>> prefix;

};

static std::vector<StampNode> nodes;

static size_t writeCallback(
char* ptr,
size_t size,
size_t nmemb,
void* userdata
) {
auto* output = static_caststd::string*(userdata);
output->append(ptr, size * nmemb);
return size * nmemb;
}

class HttpClient {
public:
explicit HttpClient(std::string baseUrl)
: baseUrl_(std::move(baseUrl)),
curl_(curl_easy_init()) {
if (curl_ == nullptr) {
throw std::runtime_error("curl_easy_init failed");
}

text
curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, writeCallback); curl_easy_setopt(curl_, CURLOPT_NOSIGNAL, 1L); curl_easy_setopt(curl_, CURLOPT_FOLLOWLOCATION, 1L); } ~HttpClient() { if (curl_ != nullptr) { curl_easy_cleanup(curl_); } } json getJson(const std::string& path) { std::string response; const std::string url = baseUrl_ + path; curl_easy_setopt(curl_, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &response); const CURLcode result = curl_easy_perform(curl_); if (result != CURLE_OK) { throw std::runtime_error(curl_easy_strerror(result)); } long status = 0; curl_easy_getinfo(curl_, CURLINFO_RESPONSE_CODE, &status); if (status < 200 || status >= 300) { throw std::runtime_error( "HTTP error: " + std::to_string(status) ); } return json::parse(response); }

private:
std::string baseUrl_;
CURL* curl_;
};

static int64 asId(const json& value) {
if (value.is_string()) {
return std::stoll(value.getstd::string());
}

return value.get<int64>();

}

static void trim(std::string& value) {
while (!value.empty() &&
std::isspace(static_cast<unsigned char>(value.back()))) {
value.pop_back();
}

text
size_t first = 0; while (first < value.size() && std::isspace(static_cast<unsigned char>(value[first]))) { ++first; } if (first > 0) { value.erase(0, first); }

}

// Объединяет два независимых леса.
// a[x] и b[y] — лучшие значения при выборе x и y штампов.
static std::vector<int> mergeDp(
const std::vector<int>& a,
const std::vector<int>& b,
int limit
) {
const int maxA = static_cast<int>(a.size()) - 1;
const int maxB = static_cast<int>(b.size()) - 1;
const int maxResult = std::min(limit, maxA + maxB);

text
std::vector<int> result(maxResult + 1, NEG); for (int x = 0; x <= maxA && x <= limit; ++x) { if (a[x] == NEG) { continue; } const int maxY = std::min(maxB, limit - x); for (int y = 0; y <= maxY; ++y) { if (b[y] == NEG) { continue; } result[x + y] = std::max( result[x + y], a[x] + b[y] ); } } return result;

}

static void buildDp(int vertex) {
for (int child : nodes[vertex].children) {
buildDp(child);
}

text
// Здесь хранятся только выбранные штампы из детей. // Для самого vertex потребуется ещё одно место. std::vector<int> merged(1, 0); nodes[vertex].prefix.clear(); nodes[vertex].prefix.reserve(nodes[vertex].children.size()); for (int child : nodes[vertex].children) { nodes[vertex].prefix.push_back(merged); merged = mergeDp(merged, nodes[child].dp, K - 1); } const int maxTake = std::min( K, static_cast<int>(merged.size()) ); nodes[vertex].dp.assign(maxTake + 1, NEG); nodes[vertex].dp[0] = 0; for (int fromChildren = 0; fromChildren < static_cast<int>(merged.size()) && fromChildren + 1 <= K; ++fromChildren) { if (merged[fromChildren] == NEG) { continue; } nodes[vertex].dp[fromChildren + 1] = nodes[vertex].rarity + merged[fromChildren]; }

}

static void restoreNode(
int vertex,
int takeCount,
std::vector<int64>& answer
);

static void restoreIndependentTrees(
const std::vector<int>& treeRoots,
const std::vector<std::vector<int>>& prefix,
int takeCount,
int targetValue,
std::vector<int64>& answer
) {
for (int i = static_cast<int>(treeRoots.size()) - 1;
i >= 0;
--i) {
const int root = treeRoots[i];
const std::vector<int>& previous = prefix[i];

text
int chosenFromRoot = -1; const int maxFromRoot = std::min( takeCount, static_cast<int>(nodes[root].dp.size()) - 1 ); for (int fromRoot = 0; fromRoot <= maxFromRoot; ++fromRoot) { const int fromPrevious = takeCount - fromRoot; if (fromPrevious >= static_cast<int>(previous.size())) { continue; } if (previous[fromPrevious] == NEG || nodes[root].dp[fromRoot] == NEG) { continue; } if (previous[fromPrevious] + nodes[root].dp[fromRoot] == targetValue) { chosenFromRoot = fromRoot; break; } } if (chosenFromRoot < 0) { throw std::runtime_error("DP reconstruction failed"); } takeCount -= chosenFromRoot; targetValue = previous[takeCount]; if (chosenFromRoot > 0) { restoreNode(root, chosenFromRoot, answer); } }

}

static void restoreNode(
int vertex,
int takeCount,
std::vector<int64>& answer
) {
// Родитель добавляется до всех своих зависимых штампов.
answer.push_back(nodes[vertex].id);

text
const int fromChildren = takeCount - 1; const int childrenValue = nodes[vertex].dp[takeCount] - nodes[vertex].rarity; restoreIndependentTrees( nodes[vertex].children, nodes[vertex].prefix, fromChildren, childrenValue, answer );

}

int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);

text
std::string baseUrl; std::string tourId; std::getline(std::cin, baseUrl); std::getline(std::cin, tourId); trim(baseUrl); trim(tourId); if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) { std::cout << 0 << '\n'; return 0; } try { HttpClient client(baseUrl); const json tour = client.getJson("/tour/" + tourId); const int64 startIsland = asId(tour.at("start_island")); K = static_cast<int>(asId(tour.at("max_stamps"))); const json islandsJson = client.getJson("/islands?tour_id=" + tourId); std::unordered_map<int64, int> islandIndex; islandIndex.reserve( islandsJson.at("islands").size() * 2 + 1 ); for (const json& island : islandsJson.at("islands")) { const int64 id = asId(island); if (!islandIndex.count(id)) { const int index = static_cast<int>(islandIndex.size()); islandIndex.emplace(id, index); } } const int islandCount = static_cast<int>(islandIndex.size()); std::vector<std::vector<int>> routes(islandCount); const json routesJson = client.getJson("/routes?tour_id=" + tourId); for (const json& route : routesJson.at("routes")) { const int64 fromId = asId(route.at("from")); const int64 toId = asId(route.at("to")); const auto fromIt = islandIndex.find(fromId); const auto toIt = islandIndex.find(toId); if (fromIt != islandIndex.end() && toIt != islandIndex.end()) { routes[fromIt->second].push_back(toIt->second); } } std::vector<char> reachable(islandCount, false); std::vector<int> stack; const auto startIt = islandIndex.find(startIsland); if (startIt != islandIndex.end()) { reachable[startIt->second] = true; stack.push_back(startIt->second); } while (!stack.empty()) { const int vertex = stack.back(); stack.pop_back(); for (int next : routes[vertex]) { if (!reachable[next]) { reachable[next] = true; stack.push_back(next); } } } const json stampsJson = client.getJson("/stamps?tour_id=" + tourId); const json& stamps = stampsJson.at("stamps"); const int n = static_cast<int>(stamps.size()); K = std::min(K, n); if (K <= 0 || n == 0) { std::cout << 0 << '\n'; curl_global_cleanup(); return 0; } nodes.assign(n, StampNode{}); std::vector<int64> stampIsland(n); std::vector<int64> requirementId(n); std::vector<char> hasRequirement(n, false); std::unordered_map<int64, int> stampIndex; stampIndex.reserve(n * 2 + 1); for (int i = 0; i < n; ++i) { const json& stamp = stamps[i]; nodes[i].id = asId(stamp.at("stamp_id")); nodes[i].rarity = stamp.at("rarity").get<int>(); stampIsland[i] = asId(stamp.at("island_id")); const auto requirementsIt = stamp.find("requires"); if (requirementsIt != stamp.end() && requirementsIt->is_array() && !requirementsIt->empty()) { hasRequirement[i] = true; requirementId[i] = asId((*requirementsIt)[0]); } stampIndex[nodes[i].id] = i; } std::vector<int> parent(n, -1); std::vector<std::vector<int>> dependencyChildren(n); std::vector<char> blocked(n, false); for (int i = 0; i < n; ++i) { const auto islandIt = islandIndex.find(stampIsland[i]); if (islandIt == islandIndex.end() || !reachable[islandIt->second]) { blocked[i] = true; } if (hasRequirement[i]) { const auto requirementIt = stampIndex.find(requirementId[i]); if (requirementIt == stampIndex.end()) { blocked[i] = true; } else { parent[i] = requirementIt->second; dependencyChildren[parent[i]].push_back(i); } } } // Если штамп недоступен, все зависящие от него штампы // также недоступны. std::queue<int> blockedQueue; for (int i = 0; i < n; ++i) { if (blocked[i]) { blockedQueue.push(i); } } while (!blockedQueue.empty()) { const int vertex = blockedQueue.front(); blockedQueue.pop(); for (int child : dependencyChildren[vertex]) { if (!blocked[child]) { blocked[child] = true; blockedQueue.push(child); } } } std::vector<int> roots; roots.reserve(n); for (int i = 0; i < n; ++i) { if (blocked[i]) { continue; } if (parent[i] == -1) { roots.push_back(i); } else { nodes[parent[i]].children.push_back(i); } } if (roots.empty()) { std::cout << 0 << '\n'; curl_global_cleanup(); return 0; } for (int root : roots) { buildDp(root); } std::vector<int> totalDp(1, 0); std::vector<std::vector<int>> rootPrefix; rootPrefix.reserve(roots.size()); for (int root : roots) { rootPrefix.push_back(totalDp); totalDp = mergeDp(totalDp, nodes[root].dp, K); } int bestCount = 0; for (int count = 1; count < static_cast<int>(totalDp.size()); ++count) { if (totalDp[count] > totalDp[bestCount]) { bestCount = count; } } if (bestCount == 0) { std::cout << 0 << '\n'; curl_global_cleanup(); return 0; } std::vector<int64> answer; answer.reserve(bestCount); restoreIndependentTrees( roots, rootPrefix, bestCount, totalDp[bestCount], answer ); std::cout << answer.size() << '\n'; for (size_t i = 0; i < answer.size(); ++i) { if (i > 0) { std::cout << ' '; } std::cout << answer[i]; } std::cout << '\n'; } catch (...) { // При штатной работе локального сервера сюда выполнение // попадать не должно. std::cout << 0 << '\n'; } curl_global_cleanup(); return 0;

}

ID
Вердикт
Время
Память

1

Тест пройден
574 мс
660 кБ

Бенчмарк учитывается в зачёте и проверяется несколько раз на стабильность. Результатом считается среднее время
ID
Вердикт
Время
Память

2

Тест пройден
579 мс
660 кБ

3

Тест пройден
574 мс
804 кБ

4

Тест пройден
574 мс
660 кБ

5

Тест пройден
573 мс
672 кБ

6

Тест пройден
574 мс
672 кБ

7

Тест пройден
574 мс
804 кБ

8

Тест пройден
574 мс
944 кБ

9

Тест пройден
574 мс
940 кБ

10

Тест пройден
575 мс
668 кБ

11

Тест пройден
574 мс
932 кБ

12

Тест пройден
579 мс
676 кБ

Optimize the solution

Pensiero

Optimizing HTTP request structure

I don't need to test right now, but my attempt to use sed didn't work due to &&. I'll inspect the code cleanup and might write it from scratch. I could compile just the HTTP class, excluding json, and network class separately. We should keep the update cadence in mind while deciding on using /islands. It's likely safer to fetch all four concurrently even if it's not strictly necessary.

Finalizing requests for optimization

I should note that if routes or stamps reference IDs not present in /islands, I should keep the endpoint. But such data would likely violate scenario semantics. I could simplify the patch: replace HttpClient with a batch method, keep the islands endpoint, and run requests concurrently for correctness. If the server is serial, then adding an extra request increases the wall time, so parallelism is key for optimization.

Thought for 2m 49s

Risposta

Главный bottleneck — четыре последовательных HTTP-запроса. По времени 573–579 мс видно, что DP почти не влияет на результат.

Оптимизации:

  1. /tour, /routes и /stamps запрашиваются одновременно через curl_multi.
  2. Запрос /islands удалён: все острова, важные для решения, встречаются как стартовый остров, конец переправы или остров штампа.
  3. Так как rarity >= 1, оптимальный ответ всегда содержит ровно min(max_stamps, число доступных штампов) элементов.
  4. DP ограничивается актуальным размером поддеревьев и значением K.

Сетевая часть теперь занимает примерно максимум из задержек трёх запросов вместо их суммы.

cpp
#include <algorithm> #include <cctype> #include <iostream> #include <queue> #include <stdexcept> #include <string> #include <unordered_map> #include <utility> #include <vector> #include <curl/curl.h> #include "json.hpp" using json = nlohmann::json; using int64 = long long; static constexpr int NEG = -1'000'000'000; static int K; struct StampNode { int64 id = 0; int rarity = 0; std::vector<int> children; // dp[x] — максимум при выборе x штампов из поддерева. // При x > 0 текущая вершина обязательно выбрана. std::vector<int> dp; // prefix[i] — результат объединения детей [0, i). std::vector<std::vector<int>> prefix; }; static std::vector<StampNode> nodes; static size_t writeCallback( char* ptr, size_t size, size_t nmemb, void* userdata ) { auto& output = *static_cast<std::string*>(userdata); output.append(ptr, size * nmemb); return size * nmemb; } class HttpClient { public: explicit HttpClient(std::string baseUrl) : baseUrl_(std::move(baseUrl)) { } std::vector<json> getJsonBatch( const std::vector<std::string>& paths ) const { struct Request { CURL* easy = nullptr; bool added = false; bool finished = false; CURLcode result = CURLE_FAILED_INIT; std::string url; std::string body; }; CURLM* multi = curl_multi_init(); if (multi == nullptr) { throw std::runtime_error("curl_multi_init failed"); } std::vector<Request> requests(paths.size()); auto cleanup = [&]() { if (multi == nullptr) { return; } for (Request& request : requests) { if (request.easy == nullptr) { continue; } if (request.added) { curl_multi_remove_handle(multi, request.easy); request.added = false; } curl_easy_cleanup(request.easy); request.easy = nullptr; } curl_multi_cleanup(multi); multi = nullptr; }; try { curl_multi_setopt( multi, CURLMOPT_MAX_HOST_CONNECTIONS, static_cast<long>(paths.size()) ); for (size_t i = 0; i < paths.size(); ++i) { Request& request = requests[i]; request.url = baseUrl_ + paths[i]; request.easy = curl_easy_init(); if (request.easy == nullptr) { throw std::runtime_error("curl_easy_init failed"); } curl_easy_setopt( request.easy, CURLOPT_URL, request.url.c_str() ); curl_easy_setopt( request.easy, CURLOPT_WRITEFUNCTION, writeCallback ); curl_easy_setopt( request.easy, CURLOPT_WRITEDATA, &request.body ); curl_easy_setopt( request.easy, CURLOPT_PRIVATE, &request ); curl_easy_setopt( request.easy, CURLOPT_NOSIGNAL, 1L ); curl_easy_setopt( request.easy, CURLOPT_FOLLOWLOCATION, 1L ); curl_easy_setopt( request.easy, CURLOPT_TCP_NODELAY, 1L ); const CURLMcode addResult = curl_multi_add_handle(multi, request.easy); if (addResult != CURLM_OK) { throw std::runtime_error( curl_multi_strerror(addResult) ); } request.added = true; } int running = 0; CURLMcode multiResult = curl_multi_perform(multi, &running); if (multiResult != CURLM_OK) { throw std::runtime_error( curl_multi_strerror(multiResult) ); } while (running > 0) { int descriptorCount = 0; multiResult = curl_multi_wait( multi, nullptr, 0, 1000, &descriptorCount ); if (multiResult != CURLM_OK) { throw std::runtime_error( curl_multi_strerror(multiResult) ); } multiResult = curl_multi_perform(multi, &running); if (multiResult != CURLM_OK) { throw std::runtime_error( curl_multi_strerror(multiResult) ); } } int messagesLeft = 0; while (CURLMsg* message = curl_multi_info_read( multi, &messagesLeft )) { if (message->msg != CURLMSG_DONE) { continue; } void* privateData = nullptr; curl_easy_getinfo( message->easy_handle, CURLINFO_PRIVATE, &privateData ); auto* request = static_cast<Request*>(privateData); if (request == nullptr) { throw std::runtime_error( "Missing CURL request data" ); } request->finished = true; request->result = message->data.result; } for (Request& request : requests) { if (!request.finished) { throw std::runtime_error( "HTTP request was not completed" ); } if (request.result != CURLE_OK) { throw std::runtime_error( curl_easy_strerror(request.result) ); } long status = 0; curl_easy_getinfo( request.easy, CURLINFO_RESPONSE_CODE, &status ); if (status < 200 || status >= 300) { throw std::runtime_error( "HTTP error: " + std::to_string(status) ); } } std::vector<json> result; result.reserve(requests.size()); for (const Request& request : requests) { result.push_back( json::parse(request.body) ); } cleanup(); return result; } catch (...) { cleanup(); throw; } } private: std::string baseUrl_; }; static int64 asId(const json& value) { if (value.is_string()) { return std::stoll( value.get<std::string>() ); } return value.get<int64>(); } static void trim(std::string& value) { while (!value.empty() && std::isspace( static_cast<unsigned char>(value.back()) )) { value.pop_back(); } size_t first = 0; while (first < value.size() && std::isspace( static_cast<unsigned char>(value[first]) )) { ++first; } value.erase(0, first); } static std::vector<int> mergeDp( const std::vector<int>& left, const std::vector<int>& right, int limit ) { const int maxLeft = std::min( limit, static_cast<int>(left.size()) - 1 ); const int maxRight = std::min( limit, static_cast<int>(right.size()) - 1 ); const int maxResult = std::min( limit, maxLeft + maxRight ); std::vector<int> result( maxResult + 1, NEG ); for (int leftCount = 0; leftCount <= maxLeft; ++leftCount) { if (left[leftCount] == NEG) { continue; } const int maxRightCount = std::min( maxRight, limit - leftCount ); for (int rightCount = 0; rightCount <= maxRightCount; ++rightCount) { if (right[rightCount] == NEG) { continue; } result[leftCount + rightCount] = std::max( result[leftCount + rightCount], left[leftCount] + right[rightCount] ); } } return result; } static void buildDp(int vertex) { for (int child : nodes[vertex].children) { buildDp(child); } // Количество выбранных штампов только из детей. std::vector<int> merged(1, 0); auto& prefix = nodes[vertex].prefix; prefix.clear(); prefix.reserve( nodes[vertex].children.size() ); for (int child : nodes[vertex].children) { prefix.push_back(merged); // Одно место резервируется для vertex. merged = mergeDp( merged, nodes[child].dp, K - 1 ); } const int maxTake = std::min( K, static_cast<int>(merged.size()) ); auto& dp = nodes[vertex].dp; dp.assign(maxTake + 1, NEG); dp[0] = 0; for (int fromChildren = 0; fromChildren < static_cast<int>(merged.size()) && fromChildren + 1 <= K; ++fromChildren) { if (merged[fromChildren] == NEG) { continue; } dp[fromChildren + 1] = nodes[vertex].rarity + merged[fromChildren]; } } static void restoreNode( int vertex, int takeCount, std::vector<int64>& answer ); static void restoreIndependentTrees( const std::vector<int>& roots, const std::vector<std::vector<int>>& prefix, int takeCount, int targetValue, std::vector<int64>& answer ) { for (int i = static_cast<int>(roots.size()) - 1; i >= 0; --i) { const int root = roots[i]; const std::vector<int>& previous = prefix[i]; const int maxFromRoot = std::min( takeCount, static_cast<int>( nodes[root].dp.size() ) - 1 ); int chosenFromRoot = -1; for (int fromRoot = 0; fromRoot <= maxFromRoot; ++fromRoot) { const int fromPrevious = takeCount - fromRoot; if (fromPrevious >= static_cast<int>(previous.size())) { continue; } if (previous[fromPrevious] == NEG || nodes[root].dp[fromRoot] == NEG) { continue; } if (previous[fromPrevious] + nodes[root].dp[fromRoot] == targetValue) { chosenFromRoot = fromRoot; break; } } if (chosenFromRoot < 0) { throw std::runtime_error( "DP reconstruction failed" ); } takeCount -= chosenFromRoot; targetValue = previous[takeCount]; if (chosenFromRoot > 0) { restoreNode( root, chosenFromRoot, answer ); } } } static void restoreNode( int vertex, int takeCount, std::vector<int64>& answer ) { // Требование выводится раньше зависимых штампов. answer.push_back(nodes[vertex].id); restoreIndependentTrees( nodes[vertex].children, nodes[vertex].prefix, takeCount - 1, nodes[vertex].dp[takeCount] - nodes[vertex].rarity, answer ); } int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::string baseUrl; std::string tourId; std::getline(std::cin, baseUrl); std::getline(std::cin, tourId); trim(baseUrl); trim(tourId); if (curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK) { std::cout << 0 << '\n'; return 0; } try { HttpClient client(baseUrl); // Все запросы независимы: tour_id уже прочитан // из стандартного ввода. const std::vector<json> responses = client.getJsonBatch({ "/tour/" + tourId, "/routes?tour_id=" + tourId, "/stamps?tour_id=" + tourId }); const json& tour = responses[0]; const json& routesJson = responses[1]; const json& stampsJson = responses[2]; const int64 startIsland = asId(tour.at("start_island")); K = static_cast<int>( asId(tour.at("max_stamps")) ); struct RawStamp { int64 id = 0; int64 island = 0; int rarity = 0; bool hasRequirement = false; int64 requirement = 0; }; const json& stampArray = stampsJson.at("stamps"); const int stampCount = static_cast<int>(stampArray.size()); K = std::min(K, stampCount); if (K <= 0 || stampCount == 0) { std::cout << 0 << '\n'; curl_global_cleanup(); return 0; } std::vector<RawStamp> rawStamps; rawStamps.reserve(stampCount); for (const json& stamp : stampArray) { RawStamp raw; raw.id = asId(stamp.at("stamp_id")); raw.island = asId(stamp.at("island_id")); raw.rarity = stamp.at("rarity").get<int>(); const auto requiresIt = stamp.find("requires"); if (requiresIt != stamp.end() && requiresIt->is_array() && !requiresIt->empty()) { raw.hasRequirement = true; raw.requirement = asId((*requiresIt)[0]); } rawStamps.push_back(raw); } std::vector<std::pair<int64, int64>> rawRoutes; rawRoutes.reserve( routesJson.at("routes").size() ); for (const json& route : routesJson.at("routes")) { rawRoutes.emplace_back( asId(route.at("from")), asId(route.at("to")) ); } // Отдельный GET /islands не требуется. // Все значимые острова встречаются в startIsland, // routes или stamps. std::unordered_map<int64, int> islandIndex; islandIndex.reserve( rawRoutes.size() * 2 + rawStamps.size() + 1 ); std::vector<std::vector<int>> routes; auto getIslandIndex = [&](int64 island) { const auto it = islandIndex.find(island); if (it != islandIndex.end()) { return it->second; } const int index = static_cast<int>(routes.size()); islandIndex.emplace(island, index); routes.emplace_back(); return index; }; const int startIndex = getIslandIndex(startIsland); for (const auto& [from, to] : rawRoutes) { const int fromIndex = getIslandIndex(from); const int toIndex = getIslandIndex(to); routes[fromIndex].push_back(toIndex); } for (const RawStamp& stamp : rawStamps) { getIslandIndex(stamp.island); } std::vector<char> reachable( routes.size(), false ); std::vector<int> stack; stack.reserve(routes.size()); reachable[startIndex] = true; stack.push_back(startIndex); while (!stack.empty()) { const int vertex = stack.back(); stack.pop_back(); for (int next : routes[vertex]) { if (!reachable[next]) { reachable[next] = true; stack.push_back(next); } } } nodes.assign( stampCount, StampNode{} ); std::unordered_map<int64, int> stampIndex; stampIndex.reserve( stampCount * 2 + 1 ); for (int i = 0; i < stampCount; ++i) { nodes[i].id = rawStamps[i].id; nodes[i].rarity = rawStamps[i].rarity; stampIndex.emplace( rawStamps[i].id, i ); } std::vector<int> parent( stampCount, -1 ); std::vector<std::vector<int>> dependencyChildren(stampCount); std::vector<char> blocked( stampCount, false ); for (int i = 0; i < stampCount; ++i) { const int island = islandIndex.at( rawStamps[i].island ); if (!reachable[island]) { blocked[i] = true; } if (!rawStamps[i].hasRequirement) { continue; } const auto requirementIt = stampIndex.find( rawStamps[i].requirement ); if (requirementIt == stampIndex.end()) { blocked[i] = true; continue; } parent[i] = requirementIt->second; dependencyChildren[parent[i]] .push_back(i); } // Недоступность распространяется вниз // по дереву зависимостей. std::vector<int> blockedQueue; blockedQueue.reserve(stampCount); for (int i = 0; i < stampCount; ++i) { if (blocked[i]) { blockedQueue.push_back(i); } } for (size_t head = 0; head < blockedQueue.size(); ++head) { const int vertex = blockedQueue[head]; for (int child : dependencyChildren[vertex]) { if (!blocked[child]) { blocked[child] = true; blockedQueue.push_back(child); } } } std::vector<int> roots; roots.reserve(stampCount); int availableCount = 0; for (int i = 0; i < stampCount; ++i) { if (blocked[i]) { continue; } ++availableCount; if (parent[i] == -1) { roots.push_back(i); } else { nodes[parent[i]].children .push_back(i); } } if (availableCount == 0) { std::cout << 0 << '\n'; curl_global_cleanup(); return 0; } K = std::min(K, availableCount); for (int root : roots) { buildDp(root); } std::vector<int> totalDp(1, 0); std::vector<std::vector<int>> rootPrefix; rootPrefix.reserve(roots.size()); for (int root : roots) { rootPrefix.push_back(totalDp); totalDp = mergeDp( totalDp, nodes[root].dp, K ); } // Все редкости положительны, поэтому оптимум // использует максимально допустимое число штампов. const int bestCount = K; std::vector<int64> answer; answer.reserve(bestCount); restoreIndependentTrees( roots, rootPrefix, bestCount, totalDp[bestCount], answer ); std::cout << answer.size() << '\n'; for (size_t i = 0; i < answer.size(); ++i) { if (i != 0) { std::cout << ' '; } std::cout << answer[i]; } std::cout << '\n'; } catch (...) { std::cout << 0 << '\n'; } curl_global_cleanup(); return 0; }

Итоговая сложность вычислительной части:

  • достижимость островов: O(islands + routes);
  • обработка зависимостей: O(stamps);
  • tree knapsack: до O(stamps · K²);
  • память: O(stamps · K).

При данных ограничениях вычислительная часть обычно занимает доли миллисекунды, поэтому параллельные запросы дают основное ускорение.

Condividi questa Q&A