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

Criado em: 16 de julho de 2026

Respondido usando GPT-5.6 Thinking por Chat01

Pergunta

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

Каждый штамп можно получить на конкретном острове. У штампа есть редкость 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 <iostream>
#include <string>
#include <vector>
#include <map>

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

using json = nlohmann::json;

static std::string g_base;

static size_t writeCb(char *ptr, size_t size, size_t nmemb, void userdata) {
((std::string
)userdata)->append(ptr, size * nmemb);
return size * nmemb;
}

static json httpGetJson(const std::string &path) {
std::string resp;
CURL *curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, (g_base + path).c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resp);
curl_easy_perform(curl);
curl_easy_cleanup(curl);
return json::parse(resp, nullptr, false);
}

static long long asId(const json &v) {
if (v.is_string()) return std::stoll(v.getstd::string());
return v.get<long long>();
}

static int K;
static std::vector<std::vector<int>> kids;
static std::vector<int> rar;
static std::vector<std::vector<int>> dpv;
static std::vector<std::vector<std::vector<int>>> snaps;
static std::vector<char> selected;
static const int NEG = -1000000000;

static std::vector<int> combine(const std::vector<int> &list, int cap,
std::vector<std::vector<int>> &snapOut) {
std::vector<int> c(cap + 1, NEG);
c[0] = 0;
for (int u : list) {
snapOut.push_back(c);
std::vector<int> nc(cap + 1, NEG);
for (int j = 0; j <= cap; ++j) {
if (c[j] == NEG) continue;
for (int t = 0; t + j <= cap && t <= K; ++t) {
if (dpv[u][t] == NEG) break;
if (c[j] + dpv[u][t] > nc[j + t]) nc[j + t] = c[j] + dpv[u][t];
}
}
c.swap(nc);
}
return c;
}

static void dfsDp(int v) {
for (int u : kids[v]) dfsDp(u);
std::vector<int> comb = combine(kids[v], K - 1, snaps[v]);
dpv[v].assign(K + 1, NEG);
dpv[v][0] = 0;
for (int j = 1; j <= K; ++j)
if (comb[j - 1] != NEG) dpv[v][j] = rar[v] + comb[j - 1];
}

static void assignChildren(const std::vector<int> &list,
const std::vector<std::vector<int>> &snap,
const std::vector<int> &finalComb, int j);

static void take(int v, int j) {
selected[v] = 1;
std::vector<std::vector<int>> dummy;
std::vector<int> comb = combine(kids[v], K - 1, dummy);
assignChildren(kids[v], snaps[v], comb, j - 1);
}

static void assignChildren(const std::vector<int> &list,
const std::vector<std::vector<int>> &snap,
const std::vector<int> &finalComb, int j) {
std::vector<int> cur = finalComb;
for (int i = (int)list.size() - 1; i >= 0; --i) {
int u = list[i];
const std::vector<int> &prev = snap[i];
int cap = (int)cur.size() - 1;
for (int t = 0; t <= j && t <= K; ++t) {
if (dpv[u][t] == NEG) break;
if (j - t <= cap && prev[j - t] != NEG &&
prev[j - t] + dpv[u][t] == cur[j]) {
if (t > 0) take(u, t);
j -= t;
break;
}
}
cur = prev;
}
}

int main() {
std::string base_url, tour_id;
std::getline(std::cin, base_url);
std::getline(std::cin, tour_id);

text
while (!base_url.empty() && (base_url.back() == '\r' || base_url.back() == ' ')) base_url.pop_back(); while (!tour_id.empty() && (tour_id.back() == '\r' || tour_id.back() == ' ')) tour_id.pop_back(); g_base = base_url; curl_global_init(CURL_GLOBAL_DEFAULT); json tour = httpGetJson("/tour/" + tour_id); long long startIsland = asId(tour["start_island"]); K = tour["max_stamps"].get<int>(); json isl = httpGetJson("/islands?tour_id=" + tour_id); std::map<long long, int> islIdx; for (auto &v : isl["islands"]) { long long id = asId(v); if (!islIdx.count(id)) islIdx[id] = (int)islIdx.size(); } int ni = (int)islIdx.size(); std::vector<std::vector<int>> adj(ni); json rts = httpGetJson("/routes?tour_id=" + tour_id); for (auto &r : rts["routes"]) { long long a = asId(r["from"]), b = asId(r["to"]); if (islIdx.count(a) && islIdx.count(b)) adj[islIdx[a]].push_back(islIdx[b]); } std::vector<char> reach(ni, 0); if (islIdx.count(startIsland)) { std::vector<int> st = {islIdx[startIsland]}; reach[st[0]] = 1; while (!st.empty()) { int v = st.back(); st.pop_back(); for (int u : adj[v]) if (!reach[u]) { reach[u] = 1; st.push_back(u); } } } json stj = httpGetJson("/stamps?tour_id=" + tour_id); auto &sarr = stj["stamps"]; int n = (int)sarr.size(); std::vector<long long> sid(n), sisland(n), reqId(n, -1); std::map<long long, int> sIdx; rar.assign(n, 0); for (int i = 0; i < n; ++i) { auto &s = sarr[i]; sid[i] = asId(s["stamp_id"]); sisland[i] = asId(s["island_id"]); rar[i] = s["rarity"].get<int>(); if (s.contains("requires") && !s["requires"].empty()) reqId[i] = asId(s["requires"][0]); sIdx[sid[i]] = i; } // ---- 4. Блокировка недостижимых и осиротевших + распространение вниз ---- std::vector<int> parent(n, -1); std::vector<char> blocked(n, 0); for (int i = 0; i < n; ++i) { if (!islIdx.count(sisland[i]) || !reach[islIdx[sisland[i]]]) blocked[i] = 1; if (reqId[i] >= 0) { auto it = sIdx.find(reqId[i]); if (it == sIdx.end()) blocked[i] = 1; else parent[i] = it->second; } } for (bool changed = true; changed; ) { changed = false; for (int i = 0; i < n; ++i) if (!blocked[i] && parent[i] >= 0 && blocked[parent[i]]) { blocked[i] = 1; changed = true; } } // ---- 5. Лес + ДП + восстановление ---- kids.assign(n, {}); std::vector<int> roots; for (int i = 0; i < n; ++i) { if (blocked[i]) continue; if (parent[i] >= 0 && !blocked[parent[i]]) kids[parent[i]].push_back(i); else roots.push_back(i); } dpv.assign(n, {}); snaps.assign(n, {}); selected.assign(n, 0); for (int r : roots) dfsDp(r); std::vector<std::vector<int>> topSnaps; std::vector<int> top = combine(roots, K, topSnaps); int bestJ = 0, bestVal = 0; for (int j = 0; j <= K; ++j) if (top[j] != NEG && top[j] > bestVal) { bestVal = top[j]; bestJ = j; } if (bestJ == 0) { std::cout << 0 << '\n'; return 0; } assignChildren(roots, topSnaps, top, bestJ); // ---- 6. Preorder по выбранным: родитель всегда раньше потомка ---- std::vector<long long> order; std::vector<int> st; for (int i = (int)roots.size() - 1; i >= 0; --i) if (selected[roots[i]]) st.push_back(roots[i]); while (!st.empty()) { int v = st.back(); st.pop_back(); order.push_back(sid[v]); for (int i = (int)kids[v].size() - 1; i >= 0; --i) if (selected[kids[v][i]]) st.push_back(kids[v][i]); } std::cout << order.size() << '\n'; for (size_t i = 0; i < order.size(); ++i) std::cout << order[i] << (i + 1 == order.size() ? '\n' : ' '); return 0;

}

optimize the solution

Python

python
from itertools import product, combinations import random NEG=-10**9 def merge(a,b,K): res=[NEG]*(min(K,len(a)-1+len(b)-1)+1) for i,x in enumerate(a): if x==NEG: continue for j,y in enumerate(b): if i+j>K: break if y==NEG: continue res[i+j]=max(res[i+j],x+y) return res def solve(parent,w,K): n=len(w) ch=[[] for _ in range(n)] roots=[] for i,p in enumerate(parent): (roots if p<0 else ch[p]).append(i) dp=[None]*n pref=[None]*n def dfs(v): cur=[0] pref[v]=[] for u in ch[v]: dfs(u) pref[v].append(cur) cur=merge(cur,dp[u],K-1) maxTake=min(K,len(cur)) dv=[NEG]*(maxTake+1) dv[0]=0 for c,val in enumerate(cur): if c+1<=K: dv[c+1]=w[v]+val dp[v]=dv for r in roots: dfs(r) top=[0]; tp=[] for r in roots: tp.append(top) top=merge(top,dp[r],K) best=max(range(len(top)), key=lambda i:top[i]) ans=[] def restore(v,take): ans.append(v) count=take-1 value=dp[v][take]-w[v] for i in range(len(ch[v])-1,-1,-1): u=ch[v][i] prev=pref[v][i] chosen=-1 for t in range(min(count,len(dp[u])-1)+1): pc=count-t if pc<len(prev) and prev[pc]!=NEG and dp[u][t]!=NEG and prev[pc]+dp[u][t]==value: chosen=t; break assert chosen!=-1,(v,take,i,count,value,prev,dp[u]) pc=count-chosen value=prev[pc] count=pc if chosen: restore(u,chosen) assert count==0 and value==0 count=best; value=top[best] for i in range(len(roots)-1,-1,-1): r=roots[i] prev=tp[i] chosen=-1 for t in range(min(count,len(dp[r])-1)+1): pc=count-t if pc<len(prev) and prev[pc]!=NEG and dp[r][t]!=NEG and prev[pc]+dp[r][t]==value: chosen=t; break assert chosen!=-1 pc=count-chosen value=prev[pc]; count=pc if chosen: restore(r,chosen) return top[best],ans def brute(parent,w,K): n=len(w) best=(0,[]) for mask in range(1<<n): c=mask.bit_count() if c>K: continue ok=True for i,p in enumerate(parent): if mask>>i&1 and p>=0 and not (mask>>p&1): ok=False;break if ok: s=sum(w[i] for i in range(n) if mask>>i&1) if s>best[0]: best=(s,[i for i in range(n) if mask>>i&1]) return best for n in range(1,11): for _ in range(1000): parent=[] for i in range(n): parent.append(-1 if i==0 or random.random()<.3 else random.randrange(i)) w=[random.randint(1,10) for _ in range(n)] K=random.randint(1,n) s,ans=solve(parent,w,K) b,_=brute(parent,w,K) if s!=b: print("bad",n,parent,w,K,s,b,ans) raise SystemExit print("ok")

Pensamento

Refining HTTP client and DP code

I’m focusing on refining the flow in main() to ensure the HttpClient works well. After initializing curl_global_init, I want to handle exceptions carefully and clean up correctly. I'll also tweak timeouts on requests, clear responses after each call, and set options like CURLOPT_NOSIGNAL. The DP code setup looks good with mergeDp, and I’ll carefully manage memory.

Optimizing DP and memory management

I’m considering optimizing the DP loop by adjusting state handling and reducing memory overhead. Instead of using a sentinel like LLONG_MIN for reqId, I’ll try a boolean flag for hasReq, ensuring we don’t use unnecessary sentinels. The stampIndex will reserve space based on n*2, and I’ll make sure that the maximum allowed value for K is capped by n after parsing.

Thought for 2m 24s

Resposta

Текущий подход с DP по лесу правильный. Главная лишняя работа — take() заново вызывает combine() для каждого выбранного узла. Ниже восстановление использует уже сохранённые префиксные состояния и не пересчитывает knapsack.

Дополнительно:

  • размеры DP ограничены реальным размером поддерева;
  • блокировка недоступных зависимостей распространяется за O(n), без повторных проходов;
  • std::unordered_map используется вместо std::map;
  • один CURL-handle переиспользуется для всех запросов;
  • порядок ответа формируется непосредственно при восстановлении.
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)), curl_(curl_easy_init()) { if (curl_ == nullptr) { throw std::runtime_error("curl_easy_init failed"); } 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.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; } 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); 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); } // Здесь хранятся только выбранные штампы из детей. // Для самого 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]; 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); 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); 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; }

Сложность

Пусть n — количество штампов.

  • достижимость островов: O(islands + routes);
  • распространение недоступности: O(n);
  • DP по лесу: O(n · k²);
  • восстановление: O(n · k);
  • память: O(n · k).

При максимальных ограничениях 420 · 90² ≈ 3.4 млн DP-переходов, что с большим запасом укладывается в лимит.

Pergunta

Летом у Кодеруна полно забот: он помогает следить за сервисами на одном сервере, которые работают по циклическому расписанию с периодом T T секунд (например, T = 604800 T=604800 — ровно неделя). Всего есть n n задач; задача i i запускается ровно в момент s i si​ и выполняется ровно d i di​ секунд. Задача может переходить через конец периода и завершаться уже в начале следующего цикла.

Сервер старенький, ядро на нём всего одно. Если две задачи выполняются в один и тот же момент, они конфликтуют. Формально, задачи i i и j j конфликтуют, если множества моментов их выполнения

[ s i ,    s i + d i ) и [ s j ,    s j + d j ) ,
[si​,si​+di​)и[sj​,sj​+dj​),

взятые по модулю T T, имеют непустое пересечение (момент окончания уже не принадлежит интервалу).

Требуется выбрать как можно больше задач так, чтобы никакие две выбранные задачи не конфликтовали, и вывести максимальный размер такого набора.

Период задачи удовлетворяет ограничению 2 ≤ T ≤ 1 09 2≤T≤109, момент запуска — 0 ≤ s i < T 0≤si​<T, а длительность строго меньше периода: 1 ≤ d i < T 1≤di​<T.
Формат ввода

text
В первой строке задано одно целое число T c Tc — количество наборов входных данных. Каждый набор начинается со строки с двумя целыми числами n n и T T — количество задач и период расписания. В следующих n n строках записаны по два целых числа s i si​ и d i di​ — момент запуска и длительность задачи i i.

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

text
1 ≤ T c ≤ 15   000 1≤Tc≤15000, 1 ≤ n ≤ 1 06 1≤n≤106, 2 ≤ T ≤ 1 09 2≤T≤109, 0 ≤ s i < T 0≤si​<T, 1 ≤ d i < T 1≤di​<T, задачи внутри одного набора даны отсортированными по s i si​ в неубывающем порядке (равные значения s i si​ допускаются), то есть s i ≤ s i + 1 si​≤si+1​, суммарное значение n n по всем наборам одного файла не превосходит 1 06 106.

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

Для каждого набора входных данных выведите в отдельной строке одно целое число — максимальное количество попарно неконфликтующих задач.
Примечание

Набор 1. Период равен 12 12, шесть задач длины 3 3 запускаются в моменты 0 , 2 , 4 , 6 , 8 , 10 0,2,4,6,8,10. Каждая задача конфликтует со своими соседями по времени запуска, поэтому одновременно бесконфликтными могут быть не более трёх задач — например, стартующие в моменты 0 0, 4 4 и 8 8 (интервалы [ 0 , 3 ) [0,3), [ 4 , 7 ) [4,7), [ 8 , 11 ) [8,11) попарно не пересекаются). Ответ — 3 3.

Набор 2. Задача ( s = 8 ,   d = 5 ) (s=8,d=5) выполняется в моменты [ 8 ,   13 ) ≡ [ 8 , 10 ) ∪ [ 0 , 3 ) [8,13)≡[8,10)∪[0,3), то есть переходит через конец периода и конфликтует с задачей ( s = 1 ,   d = 3 ) (s=1,d=3), выполняющейся в [ 1 , 4 ) [1,4). Выбрать все три задачи нельзя; например, подходят ( s = 1 ,   d = 3 ) (s=1,d=3) и ( s = 5 ,   d = 2 ) (s=5,d=2) с интервалами [ 1 , 4 ) [1,4) и [ 5 , 7 ) [5,7). Ответ — 2 2.

Набор 3. Период равен 100 100, четыре задачи занимают интервалы [ 0 , 25 ) [0,25), [ 25 , 50 ) [25,50), [ 50 , 75 ) [50,75) и [ 75 , 100 ) [75,100). Они не пересекаются между собой, поэтому можно выбрать все 4 4.
Ограничения

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

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

3
6 12
0 3
2 3
4 3
6 3
8 3
10 3
3 10
1 3
5 2
8 5
4 100
0 25
25 25
50 25
75 25

Вывод

3
2
4

#include <cstdio>
#include <vector>
#include <algorithm>
#include <cstdint>
#include <string>
using namespace std;

static vector<char> inbuf;
static size_t ipos = 0;

static inline int64_t rd(){
while (ipos < inbuf.size() && (inbuf[ipos] < '0' || inbuf[ipos] > '9')) ipos++;
int64_t x = 0;
while (ipos < inbuf.size() && inbuf[ipos] >= '0' && inbuf[ipos] <= '9'){
x = x*10 + (inbuf[ipos]-'0'); ipos++;
}
return x;
}

int main(){
{
const size_t CH = 1<<20;
size_t sz = 0;
inbuf.resize(CH);
for(;;){
if(sz + CH > inbuf.size()) inbuf.resize(inbuf.size()*2);
size_t r = fread(inbuf.data()+sz, 1, CH, stdin);
sz += r;
if(r < CH) break;
}
inbuf.resize(sz);
}

text
int64_t tc = rd(); string out; out.reserve(1<<20); vector<int64_t> st, en; vector<uint32_t> sufmin, up; char nb[24]; for(int64_t tcase=0; tcase<tc; tcase++){ size_t n = (size_t)rd(); int64_t t = rd(); if(n==1){ rd(); rd(); out.push_back('1'); out.push_back('\n'); continue; } size_t m = 2*n; st.resize(m); en.resize(m); for(size_t j=0;j<n;j++){ int64_t s = rd(), d = rd(); st[j]=s; en[j]=s+d; st[j+n]=s+t; en[j+n]=s+d+t; } const uint32_t NONE = (uint32_t)m; sufmin.resize(m+1); sufmin[m] = NONE; for(size_t i=m; i-->0; ){ uint32_t b = sufmin[i+1]; sufmin[i] = (b==NONE || en[i] <= en[b]) ? (uint32_t)i : b; } size_t lg = 1; while((size_t(1)<<lg) < m) lg++; up.resize(lg*m); const int64_t* E = en.data(); const int64_t* S = st.data(); for(size_t c=0;c<m;c++){ size_t idx = (size_t)(lower_bound(S, S+m, E[c]) - S); up[c] = sufmin[idx]; } for(size_t k=1;k<lg;k++){ uint32_t* cur = up.data()+k*m; uint32_t* prev = up.data()+(k-1)*m; for(size_t c=0;c<m;c++){ uint32_t mid = prev[c]; cur[c] = (mid==NONE) ? NONE : prev[mid]; } } int64_t ans = 1; for(size_t i=0;i<n;i++){ int64_t p0 = E[i]; int64_t limit = S[i] + t; size_t idx = (size_t)(lower_bound(S, S+m, p0) - S); uint32_t c = sufmin[idx]; if(c==NONE || E[c] > limit) continue; int64_t cnt = 1; for(size_t k=lg; k-->0; ){ uint32_t c2 = up[k*m + c]; if(c2 != NONE && E[c2] <= limit){ cnt += (int64_t)1<<k; c = c2; } } if(1 + cnt > ans) ans = 1 + cnt; } int len=0; int64_t v=ans; if(v==0) nb[len++]='0'; else while(v>0){ nb[len++]='0'+(int)(v%10); v/=10; } while(len>0) out.push_back(nb[--len]); out.push_back('\n'); } fwrite(out.data(), 1, out.size(), stdout); return 0;

}

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

1

Тест пройден
3 мс
2,84 МБ

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

2

Тест пройден
4 мс
2,82 МБ

3

Тест пройден
11 мс
1,16 МБ

4

Тест пройден
1,42 с
230,49 МБ

5

Тест пройден
467 мс
230,49 МБ

6

Тест пройден
1,187 с
230,49 МБ

7

Тест пройден
475 мс
214,46 МБ

8

Тест пройден
988 мс
214,46 МБ

9

Тест пройден
585 мс
230,49 МБ

10

Тест пройден
564 мс
214,46 МБ

11

Тест пройден
372 мс
230,49 МБ

12

Тест пройден
373 мс
230,49 МБ

13

Тест пройден
579 мс
214,46 МБ

14

Тест пройден
582 мс
214,46 МБ

15

Тест пройден
406 мс
202,46 МБ

16

Тест пройден
565 мс
214,46 МБ

17

Тест пройден
589 мс
230,49 МБ

18

Тест пройден
910 мс
68,71 МБ

19

Тест пройден
452 мс
47,14 МБ

20

Тест пройден
344 мс
214,46 МБ

21

Тест пройден
613 мс
230,49 МБ

22

Тест пройден
3 мс
2,75 МБ

23

Тест пройден
10 мс
1,16 МБ

24

Тест пройден
10 мс
1,16 МБ

25

Тест пройден
3 мс
2,84 МБ

26

Тест пройден
10 мс
1,16 МБ

27

Тест пройден
4 мс
2,82 МБ

28

Тест пройден
6 мс
3,51 МБ

29

Тест пройден
15 мс
1,16 МБ

30

Тест пройден
8 мс
3,01 МБ

31

Тест пройден
641 мс
214,46 МБ

32

Тест пройден
796 мс
214,46 МБ

33

Тест пройден
471 мс
230,49 МБ

34

Тест пройден
588 мс
214,46 МБ

35

Тест пройден
964 мс
230,49 МБ

36

Тест пройден
468 мс
230,49 МБ

37

Тест пройден
4 мс
2,78 МБ

38

Тест пройден
4 мс
2,85 МБ

39

Тест пройден
5 мс
3,12 МБ

40

Тест пройден
12 мс
1,16 МБ

41

Тест пройден
14 мс
1,29 МБ

42

Тест пройден
11 мс
1,16 МБ

43

Тест пройден
1,395 с
230,49 МБ

44

Тест пройден
1,396 с
230,49 МБ

Optimize the solution

Python

python
from bisect import bisect_left import random,itertools def alg(intervals,T): n=len(intervals) s=[x[0] for x in intervals] e=[x[0]+x[1] for x in intervals] suf=[0]*(n+1) NONE=n suf[n]=NONE for i in range(n-1,-1,-1): b=suf[i+1] suf[i]=i if b==NONE or e[i]<=e[b] else b m=2*n par=[m]*m g=suf[0] for i in range(n): x=e[i] if x<T: q=bisect_left(s,x) a=suf[q] if q<n else NONE # copy0 candidate a; copy1 global g+n if a!=NONE and e[a] <= e[g]+T: par[i]=a else: par[i]=g+n # copy1 query par[i+n]=a+n if a!=NONE else m else: q=bisect_left(s,x-T) a=suf[q] if q<n else NONE par[i]=a+n if a!=NONE else m par[i+n]=m # direct query by chains ans=1 for i in range(n): lim=s[i]+T c=i; cnt=0 while c<m and (e[c%n]+(T if c>=n else 0))<=lim: cnt+=1 c=par[c] ans=max(ans,cnt) return ans def brute(intervals,T): n=len(intervals) def conflict(a,b): # sample integer? endpoints integer intervals continuous; split def segs(s,d): x=s+d if x<=T:return [(s,x)] return [(s,T),(0,x-T)] for l1,r1 in segs(*a): for l2,r2 in segs(*b): if max(l1,l2)<min(r1,r2):return True return False best=0 for mask in range(1<<n): ok=True inds=[i for i in range(n) if mask>>i&1] for a,b in itertools.combinations(inds,2): if conflict(intervals[a],intervals[b]): ok=False;break if ok:best=max(best,len(inds)) return best for n in range(1,9): for _ in range(10000): T=random.randint(2,15) arr=sorted([(random.randrange(T),random.randint(1,T-1)) for _ in range(n)]) a=alg(arr,T); b=brute(arr,T) if a!=b: print("bad",n,T,arr,a,b) raise SystemExit print("ok")

Pensamento

Final explanation in Russian and approaching optimization

I need to explain the algorithm's bottlenecks and structure clearly, including complexity and memory estimates, but without committing to exact runtime on the judge. I'll remain respectful of the code standards used (C++17) and avoid algorithm-specific headers. I'll also mention the tested memory usage locally with n=1e6 and articulate the suffix minima and copies involved. We'll focus on structuring and optimizing the explanation for clarity without over-promising performance.

Thought for 5m 7s

Resposta

Главный тормоз текущего решения — бинарные подъёмы:

cpp
up.resize(lg * 2 * n);

При n = 10^6 это около 42 миллионов uint32_t, то есть примерно 168 МБ только для up. Кроме того, таблица целиком заполняется и многократно читается.

Её можно полностью убрать.

Идея

Для каждого интервала определим next[v] — следующий интервал, который выберет стандартный жадный алгоритм: среди всех интервалов, начинающихся не раньше окончания v, берём интервал с минимальным временем окончания.

У такого перехода:

  • начало следующего интервала строго больше начала текущего;
  • окончание следующего интервала строго больше окончания текущего;
  • циклов нет.

Значит, переходы образуют лес. Развернём рёбра и обойдём этот лес DFS. На текущем пути от корня к вершине времена окончания строго убывают. Для исходной задачи i нужно найти количество последних вершин пути, чьи окончания не превосходят s[i] + T. Это один бинарный поиск по текущему пути.

Два периода хранятся только логически — массивы start и finish имеют размер n, а не 2n.

cpp
#include <cstdio> #include <cstdint> #include <limits> #include <string> #include <vector> using namespace std; class FastInput { static constexpr size_t BUFFER_SIZE = 1u << 20; char buffer[BUFFER_SIZE]; size_t position = 0; size_t length = 0; inline char getChar() { if (position == length) { length = fread(buffer, 1, BUFFER_SIZE, stdin); position = 0; if (length == 0) { return 0; } } return buffer[position++]; } public: inline uint32_t nextUInt() { char c = getChar(); while (c < '0' || c > '9') { c = getChar(); } uint32_t value = 0; do { value = value * 10u + static_cast<uint32_t>(c - '0'); c = getChar(); } while (c >= '0' && c <= '9'); return value; } }; static inline uint32_t lowerBound( const vector<uint32_t>& values, uint32_t target ) { uint32_t left = 0; uint32_t right = static_cast<uint32_t>(values.size()); while (left < right) { const uint32_t middle = left + ((right - left) >> 1); if (values[middle] < target) { left = middle + 1; } else { right = middle; } } return left; } int main() { FastInput input; const uint32_t testCount = input.nextUInt(); string output; output.reserve(static_cast<size_t>(testCount) * 8u); vector<uint32_t> start; vector<uint32_t> finish; // Сначала используется как массив суффиксных минимумов, // затем переиспользуется как стек DFS. vector<uint32_t> work; // Обратный лес переходов в формате списков смежности. vector<uint32_t> head; vector<uint32_t> sibling; constexpr uint32_t NIL = numeric_limits<uint32_t>::max(); for (uint32_t test = 0; test < testCount; ++test) { const uint32_t n = input.nextUInt(); const uint32_t period = input.nextUInt(); const uint32_t vertexCount = n * 2u; start.resize(n); finish.resize(n); for (uint32_t i = 0; i < n; ++i) { const uint32_t s = input.nextUInt(); const uint32_t d = input.nextUInt(); start[i] = s; finish[i] = s + d; } /* * work[i] — индекс интервала с минимальным finish * среди интервалов [i, n). * * При равенстве выбираем меньший индекс. */ work.resize(n + 1u); work[n] = NIL; for (uint32_t i = n; i-- > 0;) { const uint32_t rightBest = work[i + 1u]; if (rightBest == NIL || finish[i] <= finish[rightBest]) { work[i] = i; } else { work[i] = rightBest; } } const uint32_t globalBest = work[0]; /* * Вершины: * [0, n) — интервалы первого периода; * [n, 2n) — интервалы второго периода. * * vertexCount — виртуальный корень для вершин, * у которых нет следующего интервала. */ head.assign(vertexCount + 1u, NIL); sibling.resize(vertexCount); auto addChild = [&](uint32_t parent, uint32_t child) { sibling[child] = head[parent]; head[parent] = child; }; for (uint32_t i = 0; i < n; ++i) { const uint32_t end = finish[i]; uint32_t nextFirstCopy = vertexCount; uint32_t nextSecondCopy = vertexCount; if (end < period) { /* * Ищем первый старт >= end. * * Для первой копии есть два вида кандидатов: * 1. суффикс первого периода; * 2. любой интервал второго периода. */ const uint32_t first = lowerBound(start, end); const uint32_t bestSuffix = work[first]; if (bestSuffix != NIL && finish[bestSuffix] <= finish[globalBest] + period) { nextFirstCopy = bestSuffix; } else { nextFirstCopy = globalBest + n; } /* * После второй копии доступны только интервалы * того же, второго периода. */ if (bestSuffix != NIL) { nextSecondCopy = bestSuffix + n; } } else { /* * Текущий интервал заканчивается уже во втором * периоде. Следующий интервал может быть только * во второй копии. */ const uint32_t first = lowerBound(start, end - period); const uint32_t bestSuffix = work[first]; if (bestSuffix != NIL) { nextFirstCopy = bestSuffix + n; } // Для второй копии потребовался бы третий период, // который для рассматриваемого окна уже не нужен. } addChild(nextFirstCopy, i); addChild(nextSecondCopy, i + n); } /* * Суффиксные минимумы больше не нужны. * Переиспользуем их память под текущий путь DFS. */ work.clear(); work.reserve(vertexCount); auto& path = work; uint32_t answer = 1; auto enterVertex = [&](uint32_t vertex) { path.push_back(vertex); /* * Ответ нужен только для интервалов исходного периода: * именно их мы рассматриваем как первый выбранный * интервал на окружности. */ if (vertex >= n) { return; } const uint32_t limit = start[vertex] + period; /* * На пути root -> vertex окончания строго убывают. * Ищем первый элемент с end <= limit. */ uint32_t left = 0; uint32_t right = static_cast<uint32_t>(path.size()); while (left < right) { const uint32_t middle = left + ((right - left) >> 1); const uint32_t pathVertex = path[middle]; const uint32_t baseIndex = pathVertex < n ? pathVertex : pathVertex - n; /* * Максимум: * finish < 2 * 10^9, * finish + period < 3 * 10^9, * что помещается в uint32_t. */ const uint32_t endTime = finish[baseIndex] + (pathVertex >= n ? period : 0u); if (endTime > limit) { left = middle + 1; } else { right = middle; } } const uint32_t count = static_cast<uint32_t>(path.size()) - left; if (count > answer) { answer = count; } }; /* * Итеративный DFS без отдельного массива visited * и без событий входа/выхода. * * head[v] используется как изменяемый указатель * на следующего необработанного ребёнка. */ while (head[vertexCount] != NIL) { const uint32_t root = head[vertexCount]; head[vertexCount] = sibling[root]; enterVertex(root); while (!path.empty()) { const uint32_t vertex = path.back(); const uint32_t child = head[vertex]; if (child == NIL) { path.pop_back(); } else { head[vertex] = sibling[child]; enterVertex(child); } } } char digits[16]; int length = 0; do { digits[length++] = static_cast<char>('0' + answer % 10u); answer /= 10u; } while (answer != 0); while (length > 0) { output.push_back(digits[--length]); } output.push_back('\n'); } fwrite(output.data(), 1, output.size(), stdout); return 0; }

Сложность

Для одного набора:

  • суффиксные минимумы: O(n);
  • построение переходов: O(n log n), один lower_bound на задачу;
  • обход леса: O(n);
  • бинарные поиски на путях: O(n log n);
  • итоговое время: O(n log n);
  • память: O(n).

При n = 10^6 основные массивы занимают примерно 32–40 МБ вместо 200+ МБ. Также исчезает заполнение и чтение примерно 42 миллионов элементов таблицы бинарных подъёмов.

Compartilhar esta Q&A