Кодерун путешествует по архипелагу и собирает ре...
생성일: 2026년 7월 16일
답변에 사용된 모델 GPT-5.6 Thinking by Chat01
생성일: 2026년 7월 16일
답변에 사용된 모델 GPT-5.6 Thinking by Chat01
Кодерун путешествует по архипелагу и собирает редкие штампы в «Паспорт морского путешественника».
Каждый штамп можно получить на конкретном острове. У штампа есть редкость rarity. Некоторые штампы можно получить только после получения других, то есть у штампа может быть не более одного непосредственного требования, а все зависимости образуют лес без циклов.
Не все острова доступны: между ними есть ориентированные переправы, и Кодерун может получать штампы только на островах, достижимых из стартового острова.
Организаторы подготовили API-сервер с данными об островах, переправах и штампах. Ваша программа должна обращаться к этому серверу и выбирать набор штампов с максимальной суммарной редкостью.
Что считается корректным
Для каждого штампа известны:
textstamp_id — идентификатор штампа; island_id — идентификатор острова; rarity — редкость штампа; requires — список требований.
В этой задаче гарантируется, что:
textrequires содержит не более одного штампа; граф зависимостей штампов не содержит циклов.
Набор и порядок получения штампов считаются корректными, если:
textкаждый выбранный штамп расположен на острове, достижимом из стартового острова; для каждого выбранного штампа все его зависимости тоже выбраны; каждый штамп из requires появляется в выводе раньше самого штампа; общее число выбранных штампов не превосходит k.
Что нужно максимизировать
Если выбрано множество штампов S, то
score = sum(rarity) по всем штампам из S.
Требуется вывести любой корректный порядок получения не более k штампов, для которого score максимален.
Если существует несколько оптимальных ответов, допускается вывести любой.
Формат ввода
На стандартный ввод вашей программы подаются:
textв первой строке base_url локального API-сервера; во второй строке tour_id — идентификатор сценария.
Ограничения:
textbase_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 различных идентификаторов штампов в порядке получения.
Пример:
Данные из потока ввода
Данные сервера
Ответ 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;
textstd::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");
}
textcurl_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();
}
textsize_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);
textstd::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];
textint 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);
textconst 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);
textstd::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
Главный bottleneck — четыре последовательных HTTP-запроса. По времени 573–579 мс видно, что DP почти не влияет на результат.
Оптимизации:
/tour, /routes и /stamps запрашиваются одновременно через curl_multi./islands удалён: все острова, важные для решения, встречаются как стартовый остров, конец переправы или остров штампа.rarity >= 1, оптимальный ответ всегда содержит ровно min(max_stamps, число доступных штампов) элементов.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);O(stamps · K²);O(stamps · K).При данных ограничениях вычислительная часть обычно занимает доли миллисекунды, поэтому параллельные запросы дают основное ускорение.