Реши задачу на c++ Задача C. Горная трасса Имя вхо...

تم الإنشاء في: ٨ يناير ٢٠٢٦

تمت الإجابة باستخدام GPT-5.2 Thinking بواسطة Chat01

سؤال

Реши задачу на c++ Задача C. Горная трасса
Имя входного файла: stdin
Имя выходного файла: stdout
Ограничение по времени: 2 seconds
Ограничение по памяти: 512 megabytes
В окрестностях Сочи оборудован туристический горный круговой маршрут (старт и финиш на-
ходятся в одной точке). Будем моделировать этот маршрут n ступеньками одинаковой ширины,
задавая каждую i-ю ступеньку ее высотой над уровнем моря ai в метрах. Соседние ступеньки мо-
гут иметь одинаковую высоту. Сложность трассы определяется суммарным значением спусков и
подъемов. Или формально,
dif f iculty= |a1−a2|+ |a2−a3|+···+ |an−1−an|+ |an−a1|.
Оказалось, что первоначально сконструированная трасса слишком сложна для туристов. Для
того чтобы ее упростить, мы можем использовать k блоков. Ширина каждого блока совпадает с
шириной ступеньки, а высота равна 1 метру. Любой из блоков можно положить на любую ступеньку
или ранее положенный блок или не использовать совсем.
На сколько таким образом можно уменьшить сложность трассы в целом?
Формат входных данных
В первой строке входных данных находятся числа N (2 ⩽ N ⩽ 106) и k (1 ⩽ k ⩽ 109). В следу-
ющей строке находятся N целых неотрицательных чисел — высоты каждой из ступенек (ai ⩽ 109).
Формат выходных данных
Выведите одно число — максимально возможное уменьшение сложности трассы.
Примеры
4 5
4 3 2 1
3 2
1 2 1
7 1000
Замечание
В первом примере сложность трассы составляла 6 (три спуска на 1 и подъем на 3 с учетом круго-
вой трассы). Положив один блок на предпоследнюю ступеньку и два на последнюю, мы уменьшим
сложностьна4.Этотжеответможнобылополучитьисиспользованиемвсех5блоков.Ноулучшить
ответ нельзя.
Баллы за каждую подзадачу начисляются только в случае, если все тесты для этой подзадачи
и необходимых подзадач успешно пройдены.
stdin stdout
4
2
4 3 3 2 3 2 1
8

الفكرة

Designing an optimized C++ solution

The greedy approach on small random arrays matches the brute-force solution, increasing my confidence. Now, I'll move to a C++ solution with O(n log n) complexity, paying attention to long long calculations since values up to 1e9 or 1e15 fit comfortably in 64-bit. Need fast I/O and custom data structures to improve efficiency.

Optimizing merge operations logic

In the merge process, I handle left and right merges separately. For merging left: I adjust size[i], prev[i], and next[pL] before calling the kill(L) function. When merging right, I make similar adjustments for size[i] and handle the prev and next pointers. I also track ver++ and manage the activeCount, stopping when it reaches 1.

Managing memory and optimizing merge logic

I need to compute D0 on the fly while reading the original array a. Storing in vector<long long> is feasible since n is up to 1e6, requiring about 8MB for the array and 50MB total for segments. For compression, I'll handle merging first and last elements carefully to avoid unnecessary shifts.

Optimizing segment merging for cycles

To avoid erasing large elements, I’ll merge the last element into the first if they are equal. Specifically, I’ll do len[0] += len.back(); and pop the last item. Even though the adjacency order changes, it’s fine for a cyclic list where segments are adjacent, as long as heights differ.

Verifying segment merging and reduction for algorithm

In the Python test I did earlier, I merged into the last element, removed the first, and ensured circular links. I’ll check how this behaves with examples, like the sequence 4 3 2 1. For reductions, each local minimum needs to raise by 2 per level. This strategy is crucial because it minimizes difficulty with smaller local segments, which impacts longer-term budget costs. The greedy approach optimizes further reductions by always focusing on the least-cost option.

Finalizing C++ code and explanation for reduction algorithm

The algorithm reduces difficulty by 2 for every operation, always selecting the cheapest operation—this ensures we maximize the number of operations within the budget. For performance, the complexity is O(n log n), which is acceptable for n=1e6, and I will use fast input to keep the execution time under control.

For the output, I'll confirm the reduction through a specific test case, and the expected result for input "4 2 4 3 3 2 3 2 1" should indeed be 4.

Thought for 10m 8s

الإجابة

Идея

Сожмём маршрут в плато: подряд идущие ступеньки одинаковой высоты заменим одним узлом со:

  • h — высота плато
  • len — его ширина (сколько ступенек в плато)

Тогда сложность — это сумма по границам плато.

Ключевое наблюдение:

Если плато — строгий локальный минимум (его высота меньше высот двух соседних плато), то если поднять всё это плато на 1 метр, сложность уменьшится ровно на 2 (по 1 на каждой из двух границ), а стоимость — len блоков.

Поднимать «не минимум» не даёт выигрыша (в лучшем случае 0, часто ухудшает).

Значит мы покупаем «шаги подъёма» с одинаковой выгодой +2, но разной ценой len.
Чтобы максимизировать суммарный выигрыш при бюджете k, всегда выгодно делать следующий шаг там, где он самый дешёвый, то есть у локального минимума с минимальным len.

Поднимаем выбранный минимум сразу «пачкой» до ближайшего события:

  • либо до высоты меньшего из соседей (после чего плато с ним сольётся),
  • либо пока не кончатся блоки.

Все операции делаем на циклическом двусвязном списке плато + priority_queue (минимальная ширина).

Сложность: O(n log n) по числу плато (≤ n), память O(n).


Решение на C++ (C++17)

cpp
#include <bits/stdc++.h> using namespace std; struct FastScanner { static constexpr size_t BUFSIZE = 1 << 20; int idx = 0, size = 0; char buf[BUFSIZE]; inline char readChar() { if (idx >= size) { size = (int)fread(buf, 1, BUFSIZE, stdin); idx = 0; if (size == 0) return 0; } return buf[idx++]; } template<typename T> bool readInt(T &out) { char c; do { c = readChar(); if (!c) return false; } while (c <= ' '); bool neg = false; if (c == '-') { neg = true; c = readChar(); } T val = 0; while (c > ' ') { val = val * 10 + (c - '0'); c = readChar(); } out = neg ? -val : val; return true; } }; struct Item { long long len; int id; int ver; }; struct Cmp { bool operator()(const Item& a, const Item& b) const { if (a.len != b.len) return a.len > b.len; // min-heap by len return a.id > b.id; } }; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); FastScanner fs; int N; long long K; if (!fs.readInt(N)) return 0; fs.readInt(K); vector<long long> a(N); for (int i = 0; i < N; i++) fs.readInt(a[i]); // initial difficulty long long D0 = 0; for (int i = 0; i < N; i++) { long long x = a[i]; long long y = a[(i + 1) % N]; D0 += llabs(x - y); } if (D0 == 0) { cout << 0 << "\n"; return 0; } // compress into plateaus vector<long long> h; vector<long long> len; h.reserve(N); len.reserve(N); for (int i = 0; i < N; i++) { if (h.empty() || a[i] != h.back()) { h.push_back(a[i]); len.push_back(1); } else { len.back()++; } } // merge first and last if equal (cycle) if (h.size() > 1 && h.front() == h.back()) { len[0] += len.back(); h.pop_back(); len.pop_back(); } int M = (int)h.size(); if (M == 1) { cout << 0 << "\n"; return 0; } vector<int> prv(M), nxt(M), ver(M, 0); vector<char> alive(M, 1); for (int i = 0; i < M; i++) { prv[i] = (i - 1 + M) % M; nxt[i] = (i + 1) % M; } auto isMin = [&](int i) -> bool { if (!alive[i]) return false; int L = prv[i], R = nxt[i]; return h[i] < h[L] && h[i] < h[R]; }; priority_queue<Item, vector<Item>, Cmp> pq; for (int i = 0; i < M; i++) { if (isMin(i)) pq.push({len[i], i, ver[i]}); } long long reduction = 0; int active = M; auto kill = [&](int j) { alive[j] = 0; ver[j]++; active--; }; while (K > 0 && active > 1 && !pq.empty()) { Item it = pq.top(); pq.pop(); int i = it.id; if (!alive[i] || it.ver != ver[i] || it.len != len[i] || !isMin(i)) continue; long long s = len[i]; if (s > K) break; // cheapest step is not affordable => nothing else is int L = prv[i], R = nxt[i]; long long target = min(h[L], h[R]); long long delta = target - h[i]; // >= 1 for strict local min long long t = min(delta, K / s); if (t == 0) break; h[i] += t; K -= s * t; reduction += 2 * t; if (t < delta) break; // budget ended, and s is minimal => can't do more anywhere // reached target, merge with neighbor(s) with same height L = prv[i]; R = nxt[i]; if (L == R) { // only two nodes in the cycle if (alive[L] && h[L] == h[i]) { len[i] += len[L]; kill(L); prv[i] = i; nxt[i] = i; } } else { if (alive[L] && h[L] == h[i]) { len[i] += len[L]; int pL = prv[L]; prv[i] = pL; nxt[pL] = i; kill(L); } R = nxt[i]; if (R != i && alive[R] && h[R] == h[i]) { len[i] += len[R]; int nR = nxt[R]; nxt[i] = nR; prv[nR] = i; kill(R); } } ver[i]++; // re-check local minima among affected nodes int A = i, B = prv[i], C = nxt[i]; if (alive[A] && isMin(A)) pq.push({len[A], A, ver[A]}); if (alive[B] && isMin(B)) pq.push({len[B], B, ver[B]}); if (alive[C] && isMin(C)) pq.push({len[C], C, ver[C]}); } // reduction can't exceed initial difficulty, but algorithm won't overshoot if (reduction > D0) reduction = D0; cout << reduction << "\n"; return 0; }

Если хочешь, могу кратко пояснить на одном твоём примере по шагам (какие плато считаются минимумами, сколько поднимаем, где сливаем).

مشاركة هذا السؤال والجواب