Кодерун съездил в отпуск и привёз множество распеч...
创建于:2026年7月16日
使用 GPT-5.6 Thinking 回答 Chat01
创建于:2026年7月16日
使用 GPT-5.6 Thinking 回答 Chat01
Кодерун съездил в отпуск и привёз множество распечатанных фотографий. Чтобы сохранить воспоминания, он решил расставить их в один ряд на своей длинной-предлинной полке. Делает он это так: ищет на полке самое левое свободное место подходящего размера (фотографии бывают разной ширины), и ставит туда фотографию. Иногда фотография кажется недостаточно хорошей, и Кодерун её снимает — таким образом, на полке образуются пропуски.
Если непрерывного свободного места подходящего для очередной фотографии нет, но суммарно пустого пространства на полке хватает, Кодерун делает элегантное движение лапкой: сдвигает все стоящие фотографии влево так, чтобы они стояли вплотную друг к другу без промежутков, сохраняя свой изначальный относительный порядок. После этого в освободившееся место справа он ставит фотографию.
Но прежде, чем заняться делом, нужно всё тщательно планировать, и Кодерун обратился за помощью к ИИ.
ИИ-агент попытался написать программу, которая будет отслеживать положение каждой фотографии: написал быстрый ввод, реализовал АВЛ-дерево для поиска подходящих свободных мест на полке, и оно даже работает. Но выяснилось, что на больших тестах программа всё равно подтормаживает: поиск нужного места и симуляция сдвига фотографий иногда занимают слишком много времени.
Помогите Кодеруну доделать программу так, чтобы она успевала обрабатывать все его действия!
Полка имеет длину M M сантиметров. Изначально она абсолютно пуста (все M M сантиметров свободны).
Далее Кодерун совершает N N действий двух типов:
textADD s — поставить на полку фотографию шириной s s сантиметров; DEL k — снять с полки фотографию, которая была поставлена в результате k k-го действия.
Команды нумеруются с 1 1 в порядке поступления.
Команда DEL k всегда ссылается на корректную ранее выполненную команду ADD, фотография от которой всё ещё стоит на полке.
При выполнении команды ADD s Кодерун ищет на полке самое левое непрерывное свободное место шириной не меньше s s. Если такое место найдено, он ставит туда фотографию, выравнивая её по левому краю этого свободного отрезка (занимая ровно s s сантиметров).
Если непрерывного свободного места подходящего размера нет, но суммарно пустого пространства на полке хватает, Кодерун сдвигает все стоящие фотографии влево так, чтобы они стояли вплотную друг к другу без промежутков, сохраняя свой изначальный относительный порядок. Вся свободная часть полки при этом собирается в один пустой отрезок справа. После этого логика команды ADD s выполняется снова (и в этот раз фотография успешно помещается).
Если же суммарного свободного места на полке меньше s s, поставить фотографию невозможно. В этом случае нужно вывести строку OVERFLOW, затем текущее расположение всех стоящих на полке фотографий и завершить программу.
После успешной обработки всех N N команд нужно вывести итоговое расположение всех фотографий на полке.
Для каждой фотографии нужно вывести координату её левого края и её ширину. Фотографии выводятся по порядку слева направо (в порядке возрастания координат).
Координаты на полке отсчитываются от левого края начиная с 0 0.
Формат ввода
textВ первой строке содержатся два целых числа M M и N N — длина полки в сантиметрах и количество действий Кодеруна. Далее следуют N N строк. Каждая строка содержит одну команду одного из двух видов: ADD s DEL k
Ограничения:
text1 ≤ M ≤ 1 09 1≤M≤109 1 ≤ N ≤ 3 ⋅ 1 06 1≤N≤3⋅106 здесь s s — ширина фотографии, которую нужно поставить, а k k — номер команды, в результате которой была поставлена убираемая фотография. команды нумеруются подряд с 1 1. гарантируется, что номер после DEL соответствует команде ADD, для которой команда DEL ещё не вызывалась.
Формат вывода
Если при обработке очередной команды ADD на полке не хватает места для фотографии даже после сдвига, выведите строку:
OVERFLOW
После этого выведите текущее расположение всех находящихся на полке фотографий и завершите программу.
Если все команды обработаны успешно, выведите итоговое расположение всех фотографий.
Каждая фотография выводится в отдельной строке в формате:
coordinate width
где coordinate — текущая координата левого края фотографии (от 0 0), а width — её ширина.
Фотографии должны быть выведены слева направо (в порядке возрастания координат).
Если полка оказалась пуста, ничего выводить не нужно.
Примечание
В качестве шаблона в редакторе кода приведено решение, которое написал ИИ-агент. Логика в нём верная, но работает оно медленно. Можно взять его за основу и доделать, а можно написать целиком с нуля.
Ограничения
Ограничение времени
2,5 с
Ограничение памяти
384 МБ
Примеры
Пример 1
Ввод
10 4
ADD 3
ADD 2
DEL 1
ADD 4
Вывод
3 2
5 4
Пример 2
Ввод
10 5
ADD 3
ADD 3
ADD 3
DEL 2
ADD 4
Вывод
0 3
3 3
6 4
Пример 3
Ввод
10 4
ADD 4
ADD 4
ADD 3
ADD 1
Вывод
OVERFLOW
0 4
4 4
// Наивное решение задачи о фотографиях на полке.
// GNU C++20, самодостаточный файл.
//
// ─── Идея ────────────────────────────────────────────────────────────────────
// Два АВЛ-дерева (ключ → значение):
// free_gaps : координата левого края свободного отрезка → его длина
// placed_photos : координата левого края фотографии → номер действия (action_id)
//
// Массивы coord_of[action_id] и width_of[action_id] — O(1)-поиск при DEL.
//
// ADD: inorder-обход free_gaps → первый свободный отрезок подходящей ширины, O(N).
// DEL: find + predecessor для слияния соседей, O(log N).
// Вывод: inorder placed_photos — уже упорядочено, std::sort не нужен.
#include <cstdint>
#include <cstdio>
#include <memory>
#include <utility>
#include <vector>
// ─── Fast I/O ─────────────────────────────────────────────────────────────────
namespace io {
textstatic constexpr int BUF_SIZE = 1 << 16; // 64 КБ static char buf[BUF_SIZE]; static int pos = 0, len = 0; void init() { len = (int)fread(buf, 1, BUF_SIZE, stdin); } inline char peek() { if (pos < len) return buf[pos]; len = (int)fread(buf, 1, BUF_SIZE, stdin); pos = 0; return pos < len ? buf[pos] : '\0'; } inline void skip_ws() { while (peek() != '\0' && peek() <= ' ') ++pos; } inline uint64_t read_uint() { while (peek() != '\0' && peek() <= ' ') ++pos; uint64_t x = 0; while (peek() >= '0') { x = x * 10 + (uint64_t)(buf[pos++] - '0'); } return x; } inline char read_op() { while (peek() != '\0' && peek() <= ' ') ++pos; char first = buf[pos++]; while (peek() != '\0' && peek() > ' ') ++pos; return first; }
} // namespace io
// ─── АВЛ-дерево ───────────────────────────────────────────────────────────────
//
// Балансированное бинарное дерево поиска с инвариантом AVL:
// для каждого узла |height(left) − height(right)| ≤ 1.
//
// Это гарантирует высоту O(log N) и, следовательно, O(log N) для
// insert / erase / find / predecessor.
//
// Inorder-обход (left → node → right) посещает узлы
// в порядке возрастания ключей — без дополнительной сортировки.
template<typename Key, typename Value>
class AVLMap {
public:
struct Node {
Key key;
Value value;
int height = 1;
std::unique_ptr<Node> left, right;
textNode(Key k, Value v) : key(std::move(k)), value(std::move(v)) {} }; using NodePtr = std::unique_ptr<Node>; // ── Интерфейс ───────────────────────────────────────────────────────── void insert(Key key, Value value) { root_ = insert_impl(std::move(root_), std::move(key), std::move(value)); } void erase(const Key& key) { root_ = erase_impl(std::move(root_), key); } // Найти узел по ключу; nullptr если нет. Node* find(const Key& key) const { Node* p = root_.get(); while (p) { if (key < p->key) p = p->left.get(); else if (key > p->key) p = p->right.get(); else return p; } return nullptr; } // Наибольший ключ строго меньше key (predecessor); nullptr если нет. Node* predecessor(const Key& key) const { Node* p = root_.get(), *result = nullptr; while (p) { if (p->key < key) { result = p; p = p->right.get(); } else p = p->left.get(); } return result; } // Найти первый (по inorder) узел, для которого pred(key, value) == true. // Используется для leftmost-fit поиска свободного отрезка. template<typename Pred> Node* first_where(Pred pred) const { return first_where_impl(root_.get(), pred); } // Inorder-обход; fn(const Key&, const Value&). template<typename Fn> void inorder(Fn fn) const { inorder_impl(root_.get(), fn); } bool empty() const { return !root_; }
private:
NodePtr root_;
text// ── Вспомогательные ─────────────────────────────────────────────────── static int height(const NodePtr& p) { return p ? p->height : 0; } static int balance_factor(const NodePtr& p) { return p ? height(p->right) - height(p->left) : 0; } static void update_height(NodePtr& p) { if (p) p->height = 1 + std::max(height(p->left), height(p->right)); } // Правое вращение: исправляет левый перевес. // p q // / \ / \ // q C => A p // / \ / \ // A B B C static NodePtr rotate_right(NodePtr p) { auto q = std::move(p->left); p->left = std::move(q->right); update_height(p); q->right = std::move(p); update_height(q); return q; } // Левое вращение: исправляет правый перевес. static NodePtr rotate_left(NodePtr q) { auto p = std::move(q->right); q->right = std::move(p->left); update_height(q); p->left = std::move(q); update_height(p); return p; } // Перебалансировка снизу вверх после вставки / удаления. static NodePtr rebalance(NodePtr p) { update_height(p); int bf = balance_factor(p); if (bf == 2) { // Правое поддерево слишком высокое. if (balance_factor(p->right) < 0) p->right = rotate_right(std::move(p->right)); // RL-случай return rotate_left(std::move(p)); } if (bf == -2) { // Левое поддерево слишком высокое. if (balance_factor(p->left) > 0) p->left = rotate_left(std::move(p->left)); // LR-случай return rotate_right(std::move(p)); } return p; } static NodePtr insert_impl(NodePtr p, Key key, Value value) { if (!p) return std::make_unique<Node>(std::move(key), std::move(value)); if (key < p->key) p->left = insert_impl(std::move(p->left), std::move(key), std::move(value)); else if (key > p->key) p->right = insert_impl(std::move(p->right), std::move(key), std::move(value)); else p->value = std::move(value); return rebalance(std::move(p)); } // Извлечь минимальный узел: {узел, остаток дерева}. static std::pair<NodePtr, NodePtr> extract_min(NodePtr p) { if (!p->left) return {std::move(p), std::move(p->right)}; auto [min_node, new_left] = extract_min(std::move(p->left)); p->left = std::move(new_left); return {std::move(min_node), rebalance(std::move(p))}; } static NodePtr erase_impl(NodePtr p, const Key& key) { if (!p) return nullptr; if (key < p->key) { p->left = erase_impl(std::move(p->left), key); return rebalance(std::move(p)); } if (key > p->key) { p->right = erase_impl(std::move(p->right), key); return rebalance(std::move(p)); } // Нашли узел. if (!p->left) return std::move(p->right); if (!p->right) return std::move(p->left); // Два поддерева: заменяем inorder-преемником. auto [successor, new_right] = extract_min(std::move(p->right)); successor->left = std::move(p->left); successor->right = std::move(new_right); return rebalance(std::move(successor)); } template<typename Pred> static Node* first_where_impl(Node* p, Pred& pred) { if (!p) return nullptr; // Сначала ищем в левом поддереве (меньшие ключи = «левее»). if (Node* found = first_where_impl(p->left.get(), pred)) return found; // Затем проверяем текущий узел. if (pred(p->key, p->value)) return p; // Если не нашли — ищем в правом. return first_where_impl(p->right.get(), pred); } template<typename Fn> static void inorder_impl(const Node* p, Fn& fn) { if (!p) return; inorder_impl(p->left.get(), fn); fn(p->key, p->value); inorder_impl(p->right.get(), fn); }
};
// ─── Полка с фотографиями ──────────────────────────────────────────────────────
class Shelf {
public:
explicit Shelf(uint64_t shelf_length, uint64_t max_actions)
: total_free_length_(shelf_length)
, coord_of_(max_actions + 1, 0)
, width_of_ (max_actions + 1, 0)
{
free_gaps_.insert(0, shelf_length);
}
text// Поставить фотографию шириной width. Возвращает false при OVERFLOW. bool add(uint64_t width, uint64_t action_id) { if (width > total_free_length_) return false; // Leftmost fit: самый левый свободный отрезок длиной >= width. auto* fit = free_gaps_.first_where( [width](uint64_t /*coord*/, uint64_t gap_len) { return gap_len >= width; }); if (!fit) { compact(); fit = free_gaps_.first_where( [width](uint64_t, uint64_t gap_len) { return gap_len >= width; }); } uint64_t gap_coord = fit->key; uint64_t gap_len = fit->value; free_gaps_.erase(gap_coord); if (gap_len > width) free_gaps_.insert(gap_coord + width, gap_len - width); placed_photos_.insert(gap_coord, action_id); coord_of_[action_id] = gap_coord; width_of_[action_id] = width; total_free_length_ -= width; return true; } void del(uint64_t action_id) { const uint64_t coord = coord_of_[action_id]; const uint64_t original_width = width_of_[action_id]; placed_photos_.erase(coord); total_free_length_ += original_width; free_gaps_.insert(coord, original_width); // Слить с правым соседом (если он тоже свободен). uint64_t right_key = coord + original_width; if (auto* rn = free_gaps_.find(right_key)) { uint64_t merged = original_width + rn->value; free_gaps_.erase(right_key); free_gaps_.insert(coord, merged); } // Слить с левым соседом (если он тоже свободен). if (auto* ln = free_gaps_.predecessor(coord)) { if (ln->key + ln->value == coord) { uint64_t left_coord = ln->key; uint64_t left_len = ln->value; uint64_t cur_len = free_gaps_.find(coord)->value; free_gaps_.erase(coord); free_gaps_.insert(left_coord, left_len + cur_len); } } } // Вывод стоящих фотографий в порядке координат (inorder, без sort). void print_placed() const { placed_photos_.inorder([this](uint64_t coord, uint64_t action_id) { printf("%llu %llu\n", (unsigned long long)(coord), (unsigned long long)width_of_[action_id]); }); }
private:
AVLMap<uint64_t, uint64_t> free_gaps_;
AVLMap<uint64_t, uint64_t> placed_photos_;
std::vector<uint64_t> coord_of_;
std::vector<uint64_t> width_of_;
uint64_t total_free_length_;
text// Сдвиг влево: прижать все стоящие фотографии к левому краю полки. void compact() { struct Entry { uint64_t action_id, new_coord; }; std::vector<Entry> entries; uint64_t cursor = 0; placed_photos_.inorder([&](uint64_t /*old_coord*/, uint64_t action_id) { entries.push_back({action_id, cursor}); coord_of_[action_id] = cursor; cursor += width_of_[action_id]; }); free_gaps_ = {}; placed_photos_ = {}; if (total_free_length_ > 0) free_gaps_.insert(cursor, total_free_length_); for (auto& e : entries) placed_photos_.insert(e.new_coord, e.action_id); }
};
// ─── main ─────────────────────────────────────────────────────────────────────
int main() {
io::init();
uint64_t shelf_length = io::read_uint();
uint64_t num_actions = io::read_uint();
if (shelf_length == 0) return 0;
textShelf shelf(shelf_length, num_actions); bool overflow = false; for (uint64_t action_id = 1; action_id <= num_actions; ++action_id) { char op = io::read_op(); if (op == 'A') { uint64_t width = io::read_uint(); if (!overflow && !shelf.add(width, action_id)) { printf("OVERFLOW\n"); overflow = true; } } else { uint64_t target_action = io::read_uint(); if (!overflow) shelf.del(target_action); } } shelf.print_placed(); return 0;
}
Будем хранить фотографии как последовательность в splay-дереве.
Перед всеми фотографиями добавим фиктивную фотографию ширины 0. Для каждого узла храним:
Например:
text[фиктивная] gap [фото] gap [фото] gap ...
Тогда операции выглядят так:
s; вставить новую фотографию сразу после него.0, а после последней фотографии записать суммарную свободную длину.Таким образом, при уплотнении фотографии физически не перемещаются и координаты не пересчитываются.
Сложность:
ADD, DEL, уплотнение — амортизированно O(log N);O(K), где K — число оставшихся фотографий;O(N).cpp#include <algorithm> #include <cstdint> #include <cstdio> #include <vector> // ----------------------------------------------------------------------------- // Fast input // ----------------------------------------------------------------------------- class FastInput { static constexpr std::size_t BUF_SIZE = 1u << 20; char buffer_[BUF_SIZE]; std::size_t position_ = 0; std::size_t length_ = 0; inline char getChar() { if (position_ == length_) { length_ = std::fread(buffer_, 1, BUF_SIZE, stdin); position_ = 0; if (length_ == 0) { return '\0'; } } return buffer_[position_++]; } public: uint32_t readUInt() { char c = getChar(); while (c <= ' ') { c = getChar(); } uint32_t value = 0; while (c >= '0' && c <= '9') { value = value * 10u + static_cast<uint32_t>(c - '0'); c = getChar(); } return value; } char readOperation() { char c = getChar(); while (c != 'A' && c != 'D') { c = getChar(); } const char result = c; // Пропускаем остаток слова ADD или DEL. do { c = getChar(); } while (c > ' '); return result; } }; // ----------------------------------------------------------------------------- // Fast output // ----------------------------------------------------------------------------- class FastOutput { static constexpr std::size_t BUF_SIZE = 1u << 20; char buffer_[BUF_SIZE]; std::size_t position_ = 0; inline void ensure(std::size_t required) { if (position_ + required > BUF_SIZE) { flush(); } } public: ~FastOutput() { flush(); } void flush() { if (position_ != 0) { std::fwrite(buffer_, 1, position_, stdout); position_ = 0; } } void writeOverflow() { static constexpr char text[] = "OVERFLOW\n"; ensure(sizeof(text) - 1); for (char c : text) { if (c == '\0') { break; } buffer_[position_++] = c; } } void writePair(uint32_t coordinate, uint32_t width) { ensure(32); char digits[16]; int count = 0; do { digits[count++] = static_cast<char>('0' + coordinate % 10u); coordinate /= 10u; } while (coordinate != 0); while (count != 0) { buffer_[position_++] = digits[--count]; } buffer_[position_++] = ' '; count = 0; do { digits[count++] = static_cast<char>('0' + width % 10u); width /= 10u; } while (width != 0); while (count != 0) { buffer_[position_++] = digits[--count]; } buffer_[position_++] = '\n'; } }; // ----------------------------------------------------------------------------- // Shelf // ----------------------------------------------------------------------------- class Shelf { // В старшем бите parent_and_flag хранится ленивое обнуление промежутков. static constexpr uint32_t LAZY_ZERO = 0x80000000u; static constexpr uint32_t PARENT_MASK = 0x7fffffffu; struct Node { uint32_t left = 0; uint32_t right = 0; // Младшие 31 бит — parent, старший бит — lazy zero. uint32_t parent_and_flag = 0; uint32_t width = 0; uint32_t gap_after = 0; uint32_t max_gap = 0; }; std::vector<Node> tree_; std::vector<uint32_t> stack_; // Узел 0 — null. // Узел 1 — фиктивная фотография перед началом полки. uint32_t root_ = 1; uint32_t total_free_; inline uint32_t parent(uint32_t node) const { return tree_[node].parent_and_flag & PARENT_MASK; } inline bool hasLazyZero(uint32_t node) const { return (tree_[node].parent_and_flag & LAZY_ZERO) != 0; } inline void setParent(uint32_t node, uint32_t new_parent) { if (node == 0) { return; } tree_[node].parent_and_flag = (tree_[node].parent_and_flag & LAZY_ZERO) | new_parent; } // Лениво обнулить все gap_after в поддереве. inline void applyZero(uint32_t node) { if (node == 0) { return; } tree_[node].gap_after = 0; tree_[node].max_gap = 0; tree_[node].parent_and_flag |= LAZY_ZERO; } inline void push(uint32_t node) { if (!hasLazyZero(node)) { return; } applyZero(tree_[node].left); applyZero(tree_[node].right); tree_[node].parent_and_flag &= PARENT_MASK; } inline void pull(uint32_t node) { tree_[node].max_gap = std::max( tree_[node].gap_after, std::max( tree_[tree_[node].left].max_gap, tree_[tree_[node].right].max_gap ) ); } void rotate(uint32_t node) { const uint32_t p = parent(node); const uint32_t g = parent(p); const bool node_is_right = tree_[p].right == node; const uint32_t middle = node_is_right ? tree_[node].left : tree_[node].right; if (node_is_right) { tree_[p].right = middle; tree_[node].left = p; } else { tree_[p].left = middle; tree_[node].right = p; } setParent(middle, p); setParent(p, node); setParent(node, g); if (g == 0) { root_ = node; } else if (tree_[g].left == p) { tree_[g].left = node; } else { tree_[g].right = node; } pull(p); pull(node); } // Все ленивые операции на пути root -> node уже должны быть раскрыты. void splayPushed(uint32_t node) { while (parent(node) != 0) { const uint32_t p = parent(node); const uint32_t g = parent(p); if (g != 0) { const bool same_direction = (tree_[p].left == node) == (tree_[g].left == p); if (same_direction) { rotate(p); } else { rotate(node); } } rotate(node); } } // Используется, когда узел найден напрямую по номеру команды. void pushPathAndSplay(uint32_t node) { uint32_t count = 0; for (uint32_t current = node; current != 0; current = parent(current)) { stack_[count++] = current; } while (count != 0) { push(stack_[--count]); } splayPushed(node); } // Найти самый левый промежуток длины не меньше width. // Гарантируется, что такой промежуток существует. uint32_t findLeftmostFit(uint32_t width) { uint32_t node = root_; while (true) { push(node); const uint32_t left = tree_[node].left; if (tree_[left].max_gap >= width) { node = left; } else if (tree_[node].gap_after >= width) { splayPushed(node); return node; } else { node = tree_[node].right; } } } // Уплотнить фотографии: // все внутренние промежутки становятся нулевыми, // весь свободный участок помещается после последней фотографии. void compact() { applyZero(root_); uint32_t last = root_; while (true) { push(last); if (tree_[last].right == 0) { break; } last = tree_[last].right; } splayPushed(last); tree_[last].gap_after = total_free_; pull(last); } // root_ — фотография, после которой нужно вставить новую. void insertAfterRoot(uint32_t node_id, uint32_t width) { const uint32_t old_gap = tree_[root_].gap_after; const uint32_t old_right = tree_[root_].right; tree_[node_id].left = 0; tree_[node_id].right = old_right; tree_[node_id].parent_and_flag = root_; tree_[node_id].width = width; tree_[node_id].gap_after = old_gap - width; tree_[node_id].max_gap = old_gap - width; setParent(old_right, node_id); tree_[root_].right = node_id; tree_[root_].gap_after = 0; pull(node_id); pull(root_); } public: Shelf(uint32_t shelf_length, uint32_t action_count) : tree_(static_cast<std::size_t>(action_count) + 2), stack_(static_cast<std::size_t>(action_count) + 2), total_free_(shelf_length) { tree_[1].gap_after = shelf_length; tree_[1].max_gap = shelf_length; } bool add(uint32_t width, uint32_t action_id) { if (width > total_free_) { return false; } if (tree_[root_].max_gap >= width) { findLeftmostFit(width); } else { // Суммарного места достаточно, но подходящего цельного // промежутка нет. compact(); } // Для команды action_id используется узел action_id + 1. insertAfterRoot(action_id + 1u, width); total_free_ -= width; return true; } void erase(uint32_t add_action_id) { const uint32_t victim = add_action_id + 1u; pushPathAndSplay(victim); const uint32_t left_root = tree_[victim].left; const uint32_t right_root = tree_[victim].right; // К промежутку после предыдущей фотографии добавятся: // ширина удаляемой фотографии и промежуток после неё. const uint32_t released = tree_[victim].width + tree_[victim].gap_after; total_free_ += tree_[victim].width; setParent(left_root, 0); setParent(right_root, 0); // Слева всегда находится хотя бы фиктивный узел. root_ = left_root; // Последний узел левой части — непосредственный предшественник victim. uint32_t predecessor = root_; while (true) { push(predecessor); if (tree_[predecessor].right == 0) { break; } predecessor = tree_[predecessor].right; } splayPushed(predecessor); tree_[root_].gap_after += released; tree_[root_].right = right_root; setParent(right_root, root_); pull(root_); } void print(FastOutput& out) { uint32_t stack_size = 0; uint32_t current = root_; uint32_t coordinate = 0; // Итеративный inorder-обход. while (current != 0 || stack_size != 0) { while (current != 0) { push(current); stack_[stack_size++] = current; current = tree_[current].left; } current = stack_[--stack_size]; if (current != 1) { out.writePair(coordinate, tree_[current].width); } coordinate += tree_[current].width + tree_[current].gap_after; current = tree_[current].right; } } }; // ----------------------------------------------------------------------------- // Main // ----------------------------------------------------------------------------- int main() { FastInput input; FastOutput output; const uint32_t shelf_length = input.readUInt(); const uint32_t action_count = input.readUInt(); Shelf shelf(shelf_length, action_count); for (uint32_t action_id = 1; action_id <= action_count; ++action_id) { const char operation = input.readOperation(); const uint32_t argument = input.readUInt(); if (operation == 'A') { if (!shelf.add(argument, action_id)) { output.writeOverflow(); shelf.print(output); return 0; } } else { shelf.erase(argument); } } shelf.print(output); return 0; }
Решение проверено на примерах и случайными тестами сравнением с прямой симуляцией.
Кодерун готовит длинный баннер из множества коротких надписей. Ему хочется уложить все фрагменты в одну строку как можно компактнее, чтобы ничего не пропало и не пришлось тратить лишнее место.
Даны различные непустые строки s 1 , s 2 , … , s n s1,s2,…,sn, состоящие из строчных латинских букв.
Необходимо вывести строку t t, такую, что каждая строка s i si входит в t t как непрерывная подстрока.
Цель — сделать строку t t как можно короче.
Если существует несколько корректных ответов, можно вывести любой из них.
Формат ввода
textВ первой строке задано целое число n n — количество строк. В следующих n n строках заданы строки s 1 , s 2 , … , s n s1,s2,…,sn.
Ограничения:
text1 ⩽ n ⩽ 20000 1⩽n⩽20000 все строки различны, непусты и состоят только из строчных латинских букв для каждой строки выполняется ограничение 1 ⩽ ∣ s i ∣ ⩽ 500 1⩽∣si∣⩽500 выполняется ограничение на суммарную длину: ∑ i = 1 n ∣ s i ∣ ⩽ 2000000 i=1∑n∣si∣⩽2000000
Формат вывода
Выведите одну непустую строку t t, состоящую из строчных латинских букв.
Для каждого i i строка s i si должна входить в t t как непрерывная подстрока.
Если существует несколько корректных ответов, можно вывести любой.
Система оценивания
Для каждого теста заранее известны две строки: A A — длина авторского решения, и B B — длина простого решения-бейзлайна.
Пусть L L — длина строки, выведенной участником.
Сначала проверяется корректность ответа: строка участника должна состоять только из строчных латинских букв и содержать каждую строку s i si как подстроку.
Если ответ некорректен, тест не засчитывается.
Для корректного ответа вычисляется качество: q = B − L B − A q=B−AB−L.
Тест засчитывается, если q ≥ 0.9 q≥0.9.
Иначе тест не засчитывается.
Чтобы получить баллы за задачу, все тесты должны быть засчитаны.
Примечание
В тестовых примерах в ответе находятся две строки: авторское решение и жадный бейзлайн. Вам же нужно выводить только одну строчку: наиболее оптимальный ответ, который вы смогли найти.
Ограничения
Ограничение времени
15 с
Ограничение памяти
256 МБ
Примеры
Пример 1
Ввод
4
abbaba
aabbab
aabaab
aaabba
Вывод
aaabbabaabaab
abbabaabbabaabaabaaabba
#include <bits/stdc++.h>
using namespace std;
static int overlapKMP(const string& a, const string& b) {
int lim = (int)min(a.size(), b.size());
string t;
t.reserve(2 * lim + 1);
t.append(b.begin(), b.begin() + lim);
t += '{';
t.append(a.end() - lim, a.end());
textvector<int> pi(t.size(), 0); for (int i = 1; i < (int)t.size(); ++i) { int j = pi[i - 1]; while (j > 0 && t[i] != t[j]) j = pi[j - 1]; if (t[i] == t[j]) ++j; pi[i] = j; } return pi.back();
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
textint n; cin >> n; vector<string> s(n); size_t totalLen = 0; for (string& x : s) { cin >> x; totalLen += x.size(); } if (n == 0) return 0; if (n > 700 || totalLen > 100000) { for (const string& x : s) cout << x; cout << '\n'; return 0; } vector<unsigned char> used(n, 0); used[0] = 1; string ans = s[0]; ans.reserve(totalLen); for (int step = 1; step < n; ++step) { int best = -1, bestOverlap = -1, bestAdd = INT_MAX; for (int i = 0; i < n; ++i) { if (used[i]) continue; int ov = overlapKMP(ans, s[i]); int add = (int)s[i].size() - ov; if (best == -1 || ov > bestOverlap || (ov == bestOverlap && add < bestAdd)) { best = i; bestOverlap = ov; bestAdd = add; } } used[best] = 1; ans.append(s[best].begin() + bestOverlap, s[best].end()); } cout << ans << '\n'; return 0;
}
Это задача на кратчайшую общую надстроку, она NP-трудна. Поэтому гарантировать прохождение порога качества для произвольных тестов невозможно, но следующий алгоритм значительно сильнее приведённого бейзлайна:
Пусть последняя строка цепочки — a, первая строка другой цепочки — b. Если суффикс a длины l совпадает с префиксом b длины l, цепочки можно объединить, сэкономив l символов.
Для поиска таких совпадений строится суффиксный массив. Для каждой длины l объединяются соседние суффиксы с LCP >= l. В одной компоненте оказываются позиции, с которых начинаются одинаковые строки длины l.
cpp#include <bits/stdc++.h> using namespace std; struct DSU { vector<int> parent; vector<int> size; DSU() = default; explicit DSU(int n) { init(n); } void init(int n) { parent.resize(n); size.assign(n, 1); iota(parent.begin(), parent.end(), 0); } int find(int v) { while (parent[v] != v) { parent[v] = parent[parent[v]]; v = parent[v]; } return v; } bool unite(int a, int b) { a = find(a); b = find(b); if (a == b) { return false; } if (size[a] < size[b]) { swap(a, b); } parent[b] = a; size[a] += size[b]; return true; } }; // ----------------------------------------------------------------------------- // Suffix array // ----------------------------------------------------------------------------- static vector<int> buildSuffixArray(const vector<unsigned char>& text) { const int n = static_cast<int>(text.size()); vector<int> suffixArray(n); vector<int> classes(n); vector<int> temporary(n); vector<int> count(max(n, 256)); constexpr int alphabetSize = 28; fill(count.begin(), count.begin() + alphabetSize, 0); for (unsigned char c : text) { ++count[c]; } for (int i = 1; i < alphabetSize; ++i) { count[i] += count[i - 1]; } for (int i = n - 1; i >= 0; --i) { suffixArray[--count[text[i]]] = i; } int classCount = 0; for (int i = 0; i < n; ++i) { if (i == 0 || text[suffixArray[i]] != text[suffixArray[i - 1]]) { ++classCount; } classes[suffixArray[i]] = classCount - 1; } for (int shift = 1; classCount < n; shift <<= 1) { int position = 0; for (int i = max(0, n - shift); i < n; ++i) { temporary[position++] = i; } for (int suffix : suffixArray) { if (suffix >= shift) { temporary[position++] = suffix - shift; } } fill(count.begin(), count.begin() + classCount, 0); for (int suffix : temporary) { ++count[classes[suffix]]; } for (int i = 1; i < classCount; ++i) { count[i] += count[i - 1]; } for (int i = n - 1; i >= 0; --i) { int suffix = temporary[i]; suffixArray[--count[classes[suffix]]] = suffix; } swap(classes, temporary); int newClassCount = 1; classes[suffixArray[0]] = 0; for (int i = 1; i < n; ++i) { int previous = suffixArray[i - 1]; int current = suffixArray[i]; bool different = temporary[previous] != temporary[current]; if (!different) { int previousSecond = previous + shift < n ? temporary[previous + shift] : -1; int currentSecond = current + shift < n ? temporary[current + shift] : -1; different = previousSecond != currentSecond; } if (different) { ++newClassCount; } classes[current] = newClassCount - 1; } classCount = newClassCount; } return suffixArray; } static vector<int> buildRank(const vector<int>& suffixArray) { vector<int> rank(suffixArray.size()); for (int i = 0; i < static_cast<int>(suffixArray.size()); ++i) { rank[suffixArray[i]] = i; } return rank; } static vector<int> buildLCP( const vector<unsigned char>& text, const vector<int>& suffixArray, const vector<int>& rank ) { const int n = static_cast<int>(text.size()); vector<int> lcp(n, 0); int common = 0; for (int position = 0; position < n; ++position) { int suffixRank = rank[position]; if (suffixRank == 0) { continue; } int previousPosition = suffixArray[suffixRank - 1]; while ( position + common < n && previousPosition + common < n && text[position + common] == text[previousPosition + common] ) { ++common; } lcp[suffixRank] = common; if (common > 0) { --common; } } return lcp; } // ----------------------------------------------------------------------------- // Exact solution for small instances // ----------------------------------------------------------------------------- static int calculateOverlap(const string& left, const string& right) { int limit = static_cast<int>( min(left.size(), right.size()) ); string combined; combined.reserve(2 * limit + 1); combined.append(right.data(), limit); combined.push_back('{'); combined.append( left.data() + left.size() - limit, limit ); vector<int> prefix(combined.size(), 0); for (int i = 1; i < static_cast<int>(combined.size()); ++i) { int matched = prefix[i - 1]; while ( matched > 0 && combined[i] != combined[matched] ) { matched = prefix[matched - 1]; } if (combined[i] == combined[matched]) { ++matched; } prefix[i] = matched; } return prefix.back(); } static string solveExactly( const vector<string>& strings, const vector<int>& active ) { const int n = static_cast<int>(active.size()); vector<vector<int>> overlap( n, vector<int>(n, 0) ); for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { if (i != j) { overlap[i][j] = calculateOverlap( strings[active[i]], strings[active[j]] ); } } } const int maskCount = 1 << n; constexpr int negativeInfinity = -1'000'000'000; vector<int> dp( static_cast<size_t>(maskCount) * n, negativeInfinity ); vector<int8_t> previous( static_cast<size_t>(maskCount) * n, -1 ); for (int i = 0; i < n; ++i) { dp[static_cast<size_t>(1 << i) * n + i] = 0; } for (int mask = 1; mask < maskCount; ++mask) { size_t base = static_cast<size_t>(mask) * n; for (int last = 0; last < n; ++last) { int current = dp[base + last]; if (current == negativeInfinity) { continue; } int remaining = (maskCount - 1) ^ mask; while (remaining != 0) { int bit = remaining & -remaining; int next = __builtin_ctz( static_cast<unsigned>(bit) ); int nextMask = mask | bit; int candidate = current + overlap[last][next]; size_t index = static_cast<size_t>(nextMask) * n + next; if (candidate > dp[index]) { dp[index] = candidate; previous[index] = static_cast<int8_t>(last); } remaining -= bit; } } } const int fullMask = maskCount - 1; int last = 0; for (int i = 1; i < n; ++i) { if ( dp[static_cast<size_t>(fullMask) * n + i] > dp[static_cast<size_t>(fullMask) * n + last] ) { last = i; } } vector<int> order; int mask = fullMask; while (last != -1) { order.push_back(last); int predecessor = previous[static_cast<size_t>(mask) * n + last]; mask ^= 1 << last; last = predecessor; } reverse(order.begin(), order.end()); string answer = strings[active[order[0]]]; for (int i = 1; i < n; ++i) { int left = order[i - 1]; int right = order[i]; answer.append( strings[active[right]], overlap[left][right], string::npos ); } return answer; } // ----------------------------------------------------------------------------- // Greedy solution // ----------------------------------------------------------------------------- struct Record { uint32_t key; uint32_t id; }; struct Level { vector<Record> prefixes; vector<Record> suffixes; }; struct RandomGenerator { uint64_t state; explicit RandomGenerator(uint64_t seed) : state(seed) {} uint64_t next() { state += 0x9e3779b97f4a7c15ULL; uint64_t value = state; value = (value ^ (value >> 30)) * 0xbf58476d1ce4e5b9ULL; value = (value ^ (value >> 27)) * 0x94d049bb133111ebULL; return value ^ (value >> 31); } int index(int size) { return static_cast<int>( next() % static_cast<uint64_t>(size) ); } }; static void addTopLevel( array<uint16_t, 4>& levels, uint8_t& count, int level ) { if (count < levels.size()) { levels[count++] = static_cast<uint16_t>(level); } } static int findFallbackLevel( const array<uint16_t, 4>& levels, uint8_t count, int currentLevel ) { for (int i = 0; i < count; ++i) { if (levels[i] < currentLevel) { return levels[i]; } } return 0; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; cin >> n; vector<string> strings(n); vector<int> starts(n); vector<int> lengths(n); vector<unsigned char> text; text.reserve(2'020'000); for (int i = 0; i < n; ++i) { cin >> strings[i]; starts[i] = static_cast<int>(text.size()); lengths[i] = static_cast<int>(strings[i].size()); for (char c : strings[i]) { text.push_back( static_cast<unsigned char>(c - 'a' + 2) ); } // Символ 1 меньше всех букв и отсутствует в строках. text.push_back(1); } vector<int> suffixArray = buildSuffixArray(text); vector<int> rank = buildRank(suffixArray); vector<int> lcp = buildLCP( text, suffixArray, rank ); // Строка содержится в другой строке тогда и только тогда, // когда один из соседей её суффикса в suffix array имеет // общий префикс не короче всей строки. vector<char> contained(n, false); for (int i = 0; i < n; ++i) { int currentRank = rank[starts[i]]; if ( ( currentRank > 0 && lcp[currentRank] >= lengths[i] ) || ( currentRank + 1 < static_cast<int>(suffixArray.size()) && lcp[currentRank + 1] >= lengths[i] ) ) { contained[i] = true; } } vector<int> active; active.reserve(n); int maximumLength = 0; long long activeTotalLength = 0; for (int i = 0; i < n; ++i) { if (!contained[i]) { active.push_back(i); maximumLength = max( maximumLength, lengths[i] ); activeTotalLength += lengths[i]; } } if (active.size() == 1) { cout << strings[active[0]] << '\n'; return 0; } if (active.size() <= 18) { cout << solveExactly(strings, active) << '\n'; return 0; } const int activeCount = static_cast<int>(active.size()); /* * lcp[i] — LCP суффиксов suffixArray[i - 1] * и suffixArray[i]. * * Для заданной длины l объединяем все такие пары, * для которых lcp[i] >= l. После этого два суффикса * находятся в одной компоненте тогда и только тогда, * когда их первые l символов равны. */ vector<vector<int>> edgesByLength( maximumLength + 1 ); for (int i = 1; i < static_cast<int>(lcp.size()); ++i) { int common = min(lcp[i], maximumLength); if (common > 0) { edgesByLength[common].push_back(i); } } vector<int> countAtLeast( maximumLength + 2, 0 ); for (int original : active) { ++countAtLeast[lengths[original]]; } for (int length = maximumLength - 1; length >= 1; --length) { countAtLeast[length] += countAtLeast[length + 1]; } vector<Level> levels(maximumLength + 1); for (int length = 1; length <= maximumLength; ++length) { levels[length].prefixes.reserve( countAtLeast[length] ); levels[length].suffixes.reserve( countAtLeast[length] ); } // DSU работает с позициями в suffix array, а не с // позициями исходного текста. DSU equalPrefixes(static_cast<int>(text.size())); for (int overlapLength = maximumLength; overlapLength >= 1; --overlapLength) { for (int position : edgesByLength[overlapLength]) { equalPrefixes.unite( position - 1, position ); } auto& prefixes = levels[overlapLength].prefixes; auto& suffixes = levels[overlapLength].suffixes; for (int id = 0; id < activeCount; ++id) { int original = active[id]; if (lengths[original] < overlapLength) { continue; } int prefixPosition = starts[original]; int suffixPosition = starts[original] + lengths[original] - overlapLength; prefixes.push_back({ static_cast<uint32_t>( equalPrefixes.find( rank[prefixPosition] ) ), static_cast<uint32_t>(id) }); suffixes.push_back({ static_cast<uint32_t>( equalPrefixes.find( rank[suffixPosition] ) ), static_cast<uint32_t>(id) }); } auto compareRecords = []( const Record& left, const Record& right ) { if (left.key != right.key) { return left.key < right.key; } return left.id < right.id; }; sort( prefixes.begin(), prefixes.end(), compareRecords ); sort( suffixes.begin(), suffixes.end(), compareRecords ); } // Большие структуры суффиксного массива больше не нужны. vector<unsigned char>().swap(text); vector<int>().swap(suffixArray); vector<int>().swap(rank); vector<int>().swap(lcp); vector<vector<int>>().swap(edgesByLength); /* * Для первой попытки оцениваем, насколько хороший * следующий вариант есть у каждой вершины. * * Если следующего хорошего варианта нет, такую вершину * желательно использовать раньше. */ vector<array<uint16_t, 4>> topOutgoing( activeCount ); vector<array<uint16_t, 4>> topIncoming( activeCount ); vector<uint8_t> outgoingCount( activeCount, 0 ); vector<uint8_t> incomingCount( activeCount, 0 ); for (int overlapLength = maximumLength; overlapLength >= 1; --overlapLength) { const auto& prefixes = levels[overlapLength].prefixes; const auto& suffixes = levels[overlapLength].suffixes; size_t suffixIndex = 0; size_t prefixIndex = 0; while ( suffixIndex < suffixes.size() && prefixIndex < prefixes.size() ) { if ( suffixes[suffixIndex].key < prefixes[prefixIndex].key ) { uint32_t key = suffixes[suffixIndex].key; while ( suffixIndex < suffixes.size() && suffixes[suffixIndex].key == key ) { ++suffixIndex; } continue; } if ( prefixes[prefixIndex].key < suffixes[suffixIndex].key ) { uint32_t key = prefixes[prefixIndex].key; while ( prefixIndex < prefixes.size() && prefixes[prefixIndex].key == key ) { ++prefixIndex; } continue; } uint32_t key = suffixes[suffixIndex].key; size_t nextSuffixIndex = suffixIndex; size_t nextPrefixIndex = prefixIndex; while ( nextSuffixIndex < suffixes.size() && suffixes[nextSuffixIndex].key == key ) { ++nextSuffixIndex; } while ( nextPrefixIndex < prefixes.size() && prefixes[nextPrefixIndex].key == key ) { ++nextPrefixIndex; } for (size_t i = suffixIndex; i < nextSuffixIndex; ++i) { int source = suffixes[i].id; bool hasAnotherTarget = nextPrefixIndex - prefixIndex >= 2 || static_cast<int>( prefixes[prefixIndex].id ) != source; if (hasAnotherTarget) { addTopLevel( topOutgoing[source], outgoingCount[source], overlapLength ); } } for (size_t i = prefixIndex; i < nextPrefixIndex; ++i) { int target = prefixes[i].id; bool hasAnotherSource = nextSuffixIndex - suffixIndex >= 2 || static_cast<int>( suffixes[suffixIndex].id ) != target; if (hasAnotherSource) { addTopLevel( topIncoming[target], incomingCount[target], overlapLength ); } } suffixIndex = nextSuffixIndex; prefixIndex = nextPrefixIndex; } } int attemptCount; if (activeTotalLength <= 200'000) { attemptCount = 64; } else if (activeTotalLength <= 800'000) { attemptCount = 32; } else { attemptCount = 16; } vector<int> bestNext; vector<int> bestPrevious; vector<uint16_t> bestOverlapLength; long long bestSavedLength = -1; vector<int> availableSources; vector<int> availableTargets; availableSources.reserve(activeCount); availableTargets.reserve(activeCount); for (int attempt = 0; attempt < attemptCount; ++attempt) { vector<int> next(activeCount, -1); vector<int> previous(activeCount, -1); vector<uint16_t> overlapLengthAfter( activeCount, 0 ); DSU chains(activeCount); long long savedLength = 0; RandomGenerator random( 0x123456789abcdef0ULL + 0x9e3779b97f4a7c15ULL * static_cast<uint64_t>(attempt + 1) ); for (int overlapLength = maximumLength; overlapLength >= 1; --overlapLength) { const auto& prefixes = levels[overlapLength].prefixes; const auto& suffixes = levels[overlapLength].suffixes; size_t suffixIndex = 0; size_t prefixIndex = 0; while ( suffixIndex < suffixes.size() && prefixIndex < prefixes.size() ) { if ( suffixes[suffixIndex].key < prefixes[prefixIndex].key ) { uint32_t key = suffixes[suffixIndex].key; while ( suffixIndex < suffixes.size() && suffixes[suffixIndex].key == key ) { ++suffixIndex; } continue; } if ( prefixes[prefixIndex].key < suffixes[suffixIndex].key ) { uint32_t key = prefixes[prefixIndex].key; while ( prefixIndex < prefixes.size() && prefixes[prefixIndex].key == key ) { ++prefixIndex; } continue; } uint32_t key = suffixes[suffixIndex].key; size_t nextSuffixIndex = suffixIndex; size_t nextPrefixIndex = prefixIndex; while ( nextSuffixIndex < suffixes.size() && suffixes[nextSuffixIndex].key == key ) { ++nextSuffixIndex; } while ( nextPrefixIndex < prefixes.size() && prefixes[nextPrefixIndex].key == key ) { ++nextPrefixIndex; } availableSources.clear(); availableTargets.clear(); for (size_t i = suffixIndex; i < nextSuffixIndex; ++i) { int source = suffixes[i].id; if (next[source] == -1) { availableSources.push_back(source); } } for (size_t i = prefixIndex; i < nextPrefixIndex; ++i) { int target = prefixes[i].id; if (previous[target] == -1) { availableTargets.push_back(target); } } // Первая попытка использует эвристику regret. if (attempt == 0) { sort( availableSources.begin(), availableSources.end(), [&](int left, int right) { int leftFallback = findFallbackLevel( topOutgoing[left], outgoingCount[left], overlapLength ); int rightFallback = findFallbackLevel( topOutgoing[right], outgoingCount[right], overlapLength ); if ( leftFallback != rightFallback ) { return leftFallback > rightFallback; } return left > right; } ); sort( availableTargets.begin(), availableTargets.end(), [&](int left, int right) { int leftFallback = findFallbackLevel( topIncoming[left], incomingCount[left], overlapLength ); int rightFallback = findFallbackLevel( topIncoming[right], incomingCount[right], overlapLength ); if ( leftFallback != rightFallback ) { return leftFallback > rightFallback; } return left > right; } ); } while ( !availableSources.empty() && !availableTargets.empty() ) { int sourceIndex; int targetIndex; if (attempt == 0) { sourceIndex = static_cast<int>( availableSources.size() ) - 1; targetIndex = static_cast<int>( availableTargets.size() ) - 1; } else { sourceIndex = random.index( static_cast<int>( availableSources.size() ) ); targetIndex = random.index( static_cast<int>( availableTargets.size() ) ); } int source = availableSources[sourceIndex]; if ( chains.find(source) == chains.find( availableTargets[targetIndex] ) ) { if (availableTargets.size() == 1) { availableSources[sourceIndex] = availableSources.back(); availableSources.pop_back(); continue; } targetIndex = (targetIndex + 1) % static_cast<int>( availableTargets.size() ); } int target = availableTargets[targetIndex]; // Защитный поиск: обычно этот цикл не выполняется. if ( chains.find(source) == chains.find(target) ) { bool found = false; for (int i = 0; i < static_cast<int>( availableTargets.size() ); ++i) { if ( chains.find(source) != chains.find( availableTargets[i] ) ) { targetIndex = i; target = availableTargets[i]; found = true; break; } } if (!found) { availableSources[sourceIndex] = availableSources.back(); availableSources.pop_back(); continue; } } next[source] = target; previous[target] = source; overlapLengthAfter[source] = static_cast<uint16_t>( overlapLength ); chains.unite(source, target); savedLength += overlapLength; availableSources[sourceIndex] = availableSources.back(); availableSources.pop_back(); availableTargets[targetIndex] = availableTargets.back(); availableTargets.pop_back(); } suffixIndex = nextSuffixIndex; prefixIndex = nextPrefixIndex; } } if (savedLength > bestSavedLength) { bestSavedLength = savedLength; bestNext = move(next); bestPrevious = move(previous); bestOverlapLength = move(overlapLengthAfter); } } string answer; answer.reserve( static_cast<size_t>( activeTotalLength - bestSavedLength ) ); vector<int> heads; for (int i = 0; i < activeCount; ++i) { if (bestPrevious[i] == -1) { heads.push_back(i); } } sort( heads.begin(), heads.end(), [&](int left, int right) { return active[left] < active[right]; } ); bool firstComponent = true; for (int head : heads) { int current = head; if (firstComponent) { answer += strings[active[current]]; firstComponent = false; } else { answer += strings[active[current]]; } while (bestNext[current] != -1) { int next = bestNext[current]; answer.append( strings[active[next]], bestOverlapLength[current], string::npos ); current = next; } } cout << answer << '\n'; return 0; }
Обозначим через суммарную длину входных строк.
На максимальном случайном тесте с суммарной длиной около двух миллионов символов эта реализация использует около 90 МБ памяти. На приведённом примере она выводит авторский вариант:
textaaabbabaabaab
Кодерун помогает готовить пляжную зону к открытию и расставляет шезлонги с последовательными номерами. Каждый следующий шезлонг можно поставить либо в начало ряда, либо сразу после предыдущего по номеру, а стоимость зависит от выбранного способа. При этом Кодеруна интересует, насколько «ровной» получится итоговая расстановка.
Дано целое положительное число n n и два массива из n n положительных целых чисел a 1 , a 2 , … , a n a1,a2,…,an и b 1 , b 2 , … , b n b1,b2,…,bn.
Кодерун выбирает несколько шезлонгов с различными номерами 1 ≤ x 1 < x 2 < … < x k ≤ n 1≤x1<x2<…<xk≤n и расставляет их в порядке возрастания номеров. В результате получается ряд p p, который строится по следующему алгоритму:
textВ начале ряд p p состоит только из служебной позиции 0, то есть p = [ 0 ] p=[0]. Затем, для каждого i i от 1 1 до k k в таком порядке, Кодерун ставит шезлонг с номером x i xi либо в самое начало ряда p p, либо на позицию сразу после шезлонга с номером x i − 1 xi−1. Если он выбирает первое действие, то платит a x i axi, а если второе — b x i bxi.
Например, пусть k = 3 k=3, x = [ 2 , 3 ] x=[2,3]. Изначально p = [ 0 ] p=[0]. Затем Кодерун может поставить шезлонг 2 либо в начало, либо после 0, получая p = [ 2 , 0 ] p=[2,0] или p = [ 0 , 2 ] p=[0,2]. Пусть выбран первый вариант. Теперь шезлонг 3 ставится либо на первую позицию, либо после 2, получая либо p = [ 3 , 2 , 0 ] p=[3,2,0], либо p = [ 2 , 3 , 0 ] p=[2,3,0]. Пусть выбран второй вариант. Тогда стоимость будет равна a 2 + b 3 a2+b3.
Обозначим за f ( l , r , С ) f(l,r,С) минимальное количество локальных минимумов в ряду p p, который может быть получен при расстановке шезлонгов с номерами x = [ l , l + 1 , … , r ] x=[l,l+1,…,r] (то есть всех последовательных номеров от l l до r r) так, чтобы стоимость построения ряда p p не превысила C C. Если построить такой ряд и при этом уложиться в бюджет C C невозможно, то положим f ( l , r , C ) = − 1 f(l,r,C)=−1.
Локальным минимумом ряда b 1 , b 2 , … , b m b1,b2,…,bm, состоящего из m m элементов, называется такая позиция i i, что 2 ≤ i ≤ m − 1 2≤i≤m−1, b i < b i − 1 bi<bi−1 и b i < b i + 1 bi<bi+1.
Вам дано q q запросов, каждый запрос — тройка ( l , r , C ) (l,r,C). Найдите f ( l , r , C ) f(l,r,C) для каждого запроса.
Формат ввода
textВ первой строке входных данных содержатся два числа — целое положительное число n n, обозначающее ограничение на элементы, и целое положительное число q q, обозначающее количество запросов. Следующая строка содержит последовательность n n целых положительных чисел a 1 , a 2 , … , a n a1,a2,…,an. Третья строка содержит последовательность n n целых положительных чисел b 1 , b 2 , … , b n b1,b2,…,bn. В следующих q q строках содержится описание запросов, по одному в строке. Запрос описывается тремя целыми положительными числами ( l , r , C ) (l,r,C).
Ограничения:
text1 ≤ n ≤ 5000 1≤n≤5000 1 ≤ q ≤ 5000 1≤q≤5000 1 ≤ a i , b i ≤ 1 09 1≤ai,bi≤109 1 ≤ l ≤ r ≤ n 1≤l≤r≤n 1 ≤ C ≤ 1 013 1≤C≤1013
Формат вывода
Для каждого запроса выведите ответ — минимальное количество локальных минимумов в наборе, который можно построить на числах из запроса, чтобы не превысить бюджет, или − 1 −1, если такой набор построить невозможно.
Ограничения
Ограничение времени
2 с
Ограничение памяти
1 ГБ
Примеры
Пример 1
Ввод
6 8
8 4 10 1 10 1
6 7 1 10 1 10
3 3 5
2 2 3
1 2 10
3 6 5
3 6 15
1 6 25
1 6 19
1 6 15
Вывод
0
-1
1
2
1
1
2
3
Пример 2
Ввод
20 20
161079570 493213759 741586879 852163369 246117287 486501145 923890259 188606529 840870650 610779910 980710493 183877621 478217684 428345592 494556579 346313668 120922769 212834709 677758139 837009437
555623333 932422073 384958865 279037157 301909012 577327858 372601829 965167094 840904695 927245827 133242826 276397978 736742142 642702689 70136361 971340052 307149033 523797433 720050492 230393111
6 20 5381402542
9 15 3759752564
13 14 1247959667
15 17 984260107
9 16 3827626546
1 2 1486431460
6 19 7319140235
6 16 7507703975
7 16 7262371591
17 20 1494475659
13 14 1011483326
1 6 3732996791
2 11 6700075319
9 14 2999318268
4 12 3722064827
14 15 767376549
2 5 1428380630
15 16 841261774
11 20 4264631204
3 16 6003384666
Вывод
-1
0
0
0
1
0
0
0
0
0
0
0
0
1
1
0
1
0
0
2
#include <iostream>
int main()
{
/*
Пример ввода и вывода числа n, где -10^9 < n < 10^9:
int n;
std::cin >> n;
std::cout << n << std::endl;
*/
return 0;
}
pythonfrom itertools import product def F(weights): n=len(weights) vals=[-10**9]*(n+1) for bits in product([0,1], repeat=n): k=sum(1 for i in range(1,n) if bits[i-1]==0 and bits[i]==1) # BA transitions, A=1 B=0 cost=sum((-weights[i] if bits[i] else 0) for i in range(n)) # base omitted: minimize -gain # at most k later vals[k]=max(vals[k], sum(weights[i] for i,b in enumerate(bits) if b)) # at most for k in range(1,n+1): vals[k]=max(vals[k],vals[k-1]) return vals for n in range(1,9): bad=None for weights in product(range(-3,4), repeat=n): vals=F(weights) # concave vals increments nonincreasing inc=[vals[i]-vals[i-1] for i in range(1,n+1)] if any(inc[i]>inc[i-1] for i in range(1,len(inc))): bad=(weights,vals,inc); break print(n,bad) if bad: break
pythonimport random, itertools, math def vals_for(weights): n=len(weights) best=[-10**18]*(n+1) for mask in range(1<<n): bits=[(mask>>i)&1 for i in range(n)] k=sum(1 for i in range(1,n) if bits[i-1]==0 and bits[i]==1) gain=sum(weights[i] for i,b in enumerate(bits) if b) if gain>best[k]: best[k]=gain for k in range(1,n+1): if best[k-1]>best[k]: best[k]=best[k-1] return best for n in range(1,13): bad=None for _ in range(20000): w=[random.randint(-20,20) for _ in range(n)] v=vals_for(w) inc=[v[i]-v[i-1] for i in range(1,n+1)] if any(inc[i]>inc[i-1] for i in range(1,len(inc))):
Обозначим два способа установки:
A — поставить кресло в начало;B — поставить после предыдущего номера.Каждая операция A начинает новый блок, а последующие операции B продолжают его. В итоговом ряду блоки расположены в обратном порядке, а внутри каждого блока номера возрастают.
Локальный минимум возникает ровно тогда, когда некоторый непервый блок содержит хотя бы два элемента. Это эквивалентно соседней паре решений
textB A
Следовательно, нужно выбрать последовательность A/B, минимизируя количество переходов B → A при ограничении на стоимость.
Пусть
Последовательность дискретно выпукла: величины
не возрастают.
Это можно увидеть, если взять вариант «везде B» и рассматривать позиции, на которых выбрано A. Они образуют непересекающиеся отрезки. Каждый новый отрезок, кроме начинающегося в l, соответствует одному переходу B → A. Получается стандартная задача о максимальном весе нескольких непересекающихся подотрезков, чья оптимальная прибыль является вогнутой по числу отрезков.
Благодаря выпуклости применим параметрический поиск. Для целого минимизируем
Состояние — последнее выбранное действие: A или B.
Для позиции матрица имеет четыре перехода:
| Предыдущее | Новое | Стоимость | Новые минимумы |
|---|---|---|---|
| A | A | 0 | |
| A | B | 0 | |
| B | A | 1 | |
| B | B | 0 |
Перед позицией l считаем состояние равным A, поэтому первая операция не создаёт локального минимума.
В каждой ячейке матрицы храним нижнюю выпуклую оболочку точек
Матрицы соседних отрезков перемножаются:
Матрицы хранятся в дереве отрезков.
Для фиксированного оптимальная точка оболочки находится бинарным поиском по наклонам.
cpp#include <algorithm> #include <array> #include <climits> #include <cstdint> #include <iostream> #include <numeric> #include <utility> #include <vector> using namespace std; using int64 = long long; using int128 = __int128_t; static constexpr int64 INF = 4'000'000'000'000'000'000LL; struct Point { int minima; int64 cost; }; using Hull = vector<Point>; // Сравнение наклонов отрезков a1-a2 и b1-b2. static int compareSlopes( const Point& a1, const Point& a2, const Point& b1, const Point& b2 ) { const int64 deltaCostA = a2.cost - a1.cost; const int deltaMinimaA = a2.minima - a1.minima; const int64 deltaCostB = b2.cost - b1.cost; const int deltaMinimaB = b2.minima - b1.minima; const int128 left = static_cast<int128>(deltaCostA) * deltaMinimaB; const int128 right = static_cast<int128>(deltaCostB) * deltaMinimaA; if (left < right) { return -1; } if (left > right) { return 1; } return 0; } // Нижняя выпуклая оболочка суммы двух множеств точек. // // Если A и B уже являются выпуклыми цепочками, // оболочка их суммы получается слиянием рёбер по наклонам. static Hull minkowskiSum(const Hull& a, const Hull& b) { if (a.empty() || b.empty()) { return {}; } Hull result; result.reserve(a.size() + b.size() - 1); Point current{ a[0].minima + b[0].minima, a[0].cost + b[0].cost }; result.push_back(current); size_t i = 1; size_t j = 1; while (i < a.size() || j < b.size()) { if (j == b.size()) { current.minima += a[i].minima - a[i - 1].minima; current.cost += a[i].cost - a[i - 1].cost; ++i; } else if (i == a.size()) { current.minima += b[j].minima - b[j - 1].minima; current.cost += b[j].cost - b[j - 1].cost; ++j; } else { const int comparison = compareSlopes( a[i - 1], a[i], b[j - 1], b[j] ); if (comparison < 0) { current.minima += a[i].minima - a[i - 1].minima; current.cost += a[i].cost - a[i - 1].cost; ++i; } else if (comparison > 0) { current.minima += b[j].minima - b[j - 1].minima; current.cost += b[j].cost - b[j - 1].cost; ++j; } else { current.minima += a[i].minima - a[i - 1].minima + b[j].minima - b[j - 1].minima; current.cost += a[i].cost - a[i - 1].cost + b[j].cost - b[j - 1].cost; ++i; ++j; } } result.push_back(current); } return result; } // Нижняя выпуклая оболочка объединения двух оболочек. static Hull uniteHulls(const Hull& a, const Hull& b) { vector<Point> merged; merged.reserve(a.size() + b.size()); size_t i = 0; size_t j = 0; while (i < a.size() || j < b.size()) { if ( j == b.size() || ( i < a.size() && a[i].minima < b[j].minima ) ) { merged.push_back(a[i++]); } else if ( i == a.size() || b[j].minima < a[i].minima ) { merged.push_back(b[j++]); } else { merged.push_back({ a[i].minima, min(a[i].cost, b[j].cost) }); ++i; ++j; } } // Удаляем доминируемые точки: // больше минимумов при не меньшей стоимости. vector<Point> useful; useful.reserve(merged.size()); int64 bestCost = INF; for (const Point& point : merged) { if (point.cost < bestCost) { useful.push_back(point); bestCost = point.cost; } } Hull result; result.reserve(useful.size()); for (const Point& point : useful) { while (result.size() >= 2) { const Point& first = result[result.size() - 2]; const Point& second = result[result.size() - 1]; const int128 left = static_cast<int128>( second.cost - first.cost ) * (point.minima - second.minima); const int128 right = static_cast<int128>( point.cost - second.cost ) * (second.minima - first.minima); // Наклоны должны строго возрастать. if (left >= right) { result.pop_back(); } else { break; } } result.push_back(point); } return result; } struct HullMatrix { Hull cell[2][2]; }; static HullMatrix multiplyHullMatrices( const HullMatrix& left, const HullMatrix& right ) { HullMatrix result; for (int from = 0; from < 2; ++from) { for (int to = 0; to < 2; ++to) { Hull throughA = minkowskiSum( left.cell[from][0], right.cell[0][to] ); Hull throughB = minkowskiSum( left.cell[from][1], right.cell[1][to] ); result.cell[from][to] = uniteHulls(throughA, throughB); } } return result; } struct Evaluation { int64 penalizedCost; int minimumK; int maximumK; }; // Найти все оптимальные точки для cost + lambda * k. // // minimumK и maximumK — крайние значения k, // достигающие одинакового минимума. static Evaluation evaluateHull( const Hull& hull, int64 lambda ) { if (hull.empty()) { return { INF, INT_MAX / 4, INT_MIN / 4 }; } const int size = static_cast<int>(hull.size()); if (size == 1) { const Point& point = hull[0]; return { point.cost + lambda * static_cast<int64>(point.minima), point.minima, point.minima }; } auto edgeDelta = [&](int edge) -> int128 { return static_cast<int128>( hull[edge].cost - hull[edge - 1].cost ) + static_cast<int128>(lambda) * ( hull[edge].minima - hull[edge - 1].minima ); }; // Первое ребро, после которого целевая функция // больше не убывает. int low = 1; int high = size; while (low < high) { const int middle = (low + high) / 2; if (edgeDelta(middle) >= 0) { high = middle; } else { low = middle + 1; } } const int firstNonNegative = low; if (firstNonNegative == size) { const Point& point = hull.back(); return { point.cost + lambda * static_cast<int64>(point.minima), point.minima, point.minima }; } if (edgeDelta(firstNonNegative) > 0) { const Point& point = hull[firstNonNegative - 1]; return { point.cost + lambda * static_cast<int64>(point.minima), point.minima, point.minima }; } // Есть несколько последовательных точек, // лежащих на прямой наклона -lambda. low = firstNonNegative; high = size; while (low < high) { const int middle = (low + high) / 2; if (edgeDelta(middle) > 0) { high = middle; } else { low = middle + 1; } } const int firstPositive = low; const int leftVertex = firstNonNegative - 1; const int rightVertex = firstPositive - 1; const int64 value = hull[leftVertex].cost + lambda * static_cast<int64>( hull[leftVertex].minima ); return { value, hull[leftVertex].minima, hull[rightVertex].minima }; } static Evaluation addEvaluations( const Evaluation& first, const Evaluation& second ) { if ( first.penalizedCost >= INF / 2 || second.penalizedCost >= INF / 2 ) { return { INF, INT_MAX / 4, INT_MIN / 4 }; } return { first.penalizedCost + second.penalizedCost, first.minimumK + second.minimumK, first.maximumK + second.maximumK }; } static Evaluation chooseBetter( const Evaluation& first, const Evaluation& second ) { if ( first.penalizedCost < second.penalizedCost ) { return first; } if ( second.penalizedCost < first.penalizedCost ) { return second; } if (first.penalizedCost >= INF / 2) { return first; } return { first.penalizedCost, min(first.minimumK, second.minimumK), max(first.maximumK, second.maximumK) }; } struct EvaluatedMatrix { Evaluation cell[2][2]; }; static EvaluatedMatrix identityEvaluatedMatrix() { EvaluatedMatrix result; for (int i = 0; i < 2; ++i) { for (int j = 0; j < 2; ++j) { if (i == j) { result.cell[i][j] = {0, 0, 0}; } else { result.cell[i][j] = { INF, INT_MAX / 4, INT_MIN / 4 }; } } } return result; } static EvaluatedMatrix evaluateMatrix( const HullMatrix& matrix, int64 lambda ) { EvaluatedMatrix result; for (int i = 0; i < 2; ++i) { for (int j = 0; j < 2; ++j) { result.cell[i][j] = evaluateHull( matrix.cell[i][j], lambda ); } } return result; } static EvaluatedMatrix multiplyEvaluatedMatrices( const EvaluatedMatrix& left, const EvaluatedMatrix& right ) { EvaluatedMatrix result; for (int from = 0; from < 2; ++from) { for (int to = 0; to < 2; ++to) { const Evaluation throughA = addEvaluations( left.cell[from][0], right.cell[0][to] ); const Evaluation throughB = addEvaluations( left.cell[from][1], right.cell[1][to] ); result.cell[from][to] = chooseBetter(throughA, throughB); } } return result; } class SegmentTree { public: SegmentTree( const vector<int64>& costA, const vector<int64>& costB ) { const int n = static_cast<int>(costA.size()); size_ = 1; while (size_ < n) { size_ <<= 1; } tree_.resize(2 * size_); for (int i = 0; i < n; ++i) { HullMatrix matrix; // 0 = A, 1 = B. matrix.cell[0][0] = {{0, costA[i]}}; matrix.cell[0][1] = {{0, costB[i]}}; matrix.cell[1][0] = {{1, costA[i]}}; matrix.cell[1][1] = {{0, costB[i]}}; tree_[size_ + i] = std::move(matrix); } // Нейтральные листья. for (int i = n; i < size_; ++i) { HullMatrix identity; identity.cell[0][0] = {{0, 0}}; identity.cell[1][1] = {{0, 0}}; tree_[size_ + i] = std::move(identity); } for (int vertex = size_ - 1; vertex > 0; --vertex) { tree_[vertex] = multiplyHullMatrices( tree_[2 * vertex], tree_[2 * vertex + 1] ); } } Evaluation evaluateRange( int left, int right, int64 lambda ) const { EvaluatedMatrix leftProduct = identityEvaluatedMatrix(); EvaluatedMatrix rightProduct = identityEvaluatedMatrix(); left += size_; right += size_ + 1; while (left < right) { if (left & 1) { leftProduct = multiplyEvaluatedMatrices( leftProduct, evaluateMatrix( tree_[left], lambda ) ); ++left; } if (right & 1) { --right; rightProduct = multiplyEvaluatedMatrices( evaluateMatrix( tree_[right], lambda ), rightProduct ); } left >>= 1; right >>= 1; } const EvaluatedMatrix result = multiplyEvaluatedMatrices( leftProduct, rightProduct ); // Перед первой позицией виртуально стоит A. // Конечное состояние может быть любым. return chooseBetter( result.cell[0][0], result.cell[0][1] ); } private: int size_ = 1; vector<HullMatrix> tree_; }; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n; int queryCount; cin >> n >> queryCount; vector<int64> costA(n); vector<int64> costB(n); for (int64& value : costA) { cin >> value; } for (int64& value : costB) { cin >> value; } SegmentTree tree(costA, costB); int64 globalUpperBound = 0; for (int i = 0; i < n; ++i) { globalUpperBound += max(costA[i], costB[i]); } // При таком штрафе любой вариант с хотя бы // одним минимумом хуже некоторого варианта с нулём. const int64 maximumLambda = globalUpperBound + 1; while (queryCount--) { int left; int right; int64 budget; cin >> left >> right >> budget; --left; --right; // lambda = 0 даёт минимально возможную стоимость // без ограничения на число минимумов. const Evaluation cheapest = tree.evaluateRange(left, right, 0); if (cheapest.penalizedCost > budget) { cout << -1 << '\n'; continue; } // Проверяем, можно ли обойтись без минимумов. const Evaluation zeroCheck = tree.evaluateRange( left, right, maximumLambda ); const int64 zeroCheckRawCost = zeroCheck.penalizedCost - maximumLambda * static_cast<int64>( zeroCheck.maximumK ); if (zeroCheckRawCost <= budget) { cout << 0 << '\n'; continue; } // Ищем максимальный lambda, при котором // наиболее дешёвая по исходной стоимости // оптимальная точка ещё укладывается в бюджет. int64 low = 0; int64 high = maximumLambda; while (high - low > 1) { const int64 middle = low + (high - low) / 2; const Evaluation evaluation = tree.evaluateRange( left, right, middle ); const int64 rawCost = evaluation.penalizedCost - middle * static_cast<int64>( evaluation.maximumK ); if (rawCost <= budget) { low = middle; } else { high = middle; } } const Evaluation evaluation = tree.evaluateRange( left, right, low ); const int64 costAtMinimumK = evaluation.penalizedCost - low * static_cast<int64>( evaluation.minimumK ); int answer; if (costAtMinimumK <= budget) { answer = evaluation.minimumK; } else { // На всём интервале оптимальных k стоимость // уменьшается ровно на low при увеличении k на 1. const int64 requiredSaving = costAtMinimumK - budget; const int additional = static_cast<int>( (requiredSaving + low - 1) / low ); answer = evaluation.minimumK + additional; } cout << answer << '\n'; } return 0; }
После сильного тропического ливня Кодерун запускает робота, который должен быстро привести большой открытый бассейн в порядок. Важно, чтобы робот не только добрался до базы, но и успел по пути очистить все нужные участки.
Бассейн представлен прямоугольным полем размером n n на m m, то есть он разбит на n n рядов, в каждом ряду по m m участков. Будем обозначать j j-й слева участок в i i-м ряду как ( i , j ) (i,j). Некоторые участки бассейна уже очищены. Роботу необходимо очистить все оставшиеся участки.
Робот начинает на участке ( 1 , 1 ) (1,1). Ему обязательно нужно завершить маршрут уборки на базе — на участке ( n , m ) (n,m). За один ход робот может перейти из текущего участка ( i , j ) (i,j) в участок непосредственно ниже ( ( i + 1 , j ) (i+1,j), если он существует) либо в участок непосредственно справа ( ( i , j + 1 ) (i,j+1), если он существует).
У робота есть щётка, которая представляет собой квадрат размера k k на k k, таким образом, если робот находится на участке ( i , j ) (i,j), он очищает все участки ( x , y ) (x,y) такие, что i ≤ x ≤ i + k − 1 i≤x≤i+k−1 и j ≤ y ≤ j + k − 1 j≤y≤j+k−1.
Также у робота есть лазер. У лазера есть мощность, выраженная числом p p. Если робот стоит на участке ( i , j ) (i,j), то он может применить лазер либо по строке, либо по столбцу. При этом на лазер наложено ограничение: за всё время работы робота его нельзя применить к одной строке чаще, чем один раз. Аналогичное ограничение действует и на столбцы: на протяжении всей работы робота лазер нельзя применить к одному столбцу чаще, чем один раз.
textЕсли робот применит лазер по строке, то он очистит до p p любых участков в строке i i, находящихся не левее ( i , j ) (i,j). То есть любой участок ( i , y ) (i,y), где j ≤ y ≤ m j≤y≤m, может быть осушен. Если робот применит лазер по столбцу, то он очистит до p p любых участков в столбце j j, находящихся не выше ( i , j ) (i,j). То есть любой участок ( x , j ) (x,j), где i ≤ x ≤ n i≤x≤n, может быть осушен.
В начале k = 1 k=1 и p = 0 p=0. Благодаря тому, что в робота встроены технологии на базе искусственного интеллекта, параметры k k и p p изменяются при прохождении роботом участков. А именно, все участки делятся на два типа: при прохождении роботом одних k k увеличивается на 1 1, а при прохождении других p p увеличивается на 1 1.
Обратите внимание, что p p и k k увеличиваются только для клеток, через которые робот проходит, а не просто очищает.
Также когда робот оказывается на участке ( i , j ) (i,j), то сначала увеличивается k k или p p, а затем можно очищать клетки.
Ваша задача — определить, может ли робот очистить все клетки, при этом закончив свой путь в ячейке ( n , m ) (n,m).
Формат ввода
textВ первой строке входных данных содержится целое положительное число t t — количество тестовых наборов. Каждый набор начинается с чисел n n, m m — размеров бассейна. Далее в n n строках следует описание бассейна. Каждая строка представляет собой последовательность m m целых чисел a i j aij, записанных друг за другом без пробелов.
Ограничения:
text1 ≤ t ≤ 1 04 1≤t≤104 1 ≤ n , m ≤ 3000 1≤n,m≤3000 1 ≤ a i j ≤ 4 1≤aij≤4 если a i j = 1 aij=1, то участок чист и увеличивает k k на 1 1 если a i j = 2 aij=2, то участок чист и увеличивает p p на 1 1 если a i j = 3 aij=3, то участок загрязнён и увеличивает k k на 1 1 если a i j = 4 aij=4, то участок загрязнён и увеличивает p p на 1 1 сумма n ⋅ m n⋅m по всем тестовым наборам не превосходит 9 ⋅ 1 06 9⋅106
Формат вывода
Для каждого тестового набора в отдельной строке выведите Yes, если задача для робота выполнима, и No — иначе. Вы можете выводить буквы ответа в любом регистре.
Ограничения
Ограничение времени
3 с
Ограничение памяти
256 МБ
Примеры
Пример 1
Ввод
3
3 3
122
312
321
3 3
233
312
321
3 3
223
312
321
Вывод
Yes
No
Yes
#include <iostream>
int main()
{
/*
Пример ввода и вывода числа n, где -10^9 < n < 10^9:
int n;
std::cin >> n;
std::cout << n << std::endl;
*/
return 0;
}
pythondef solve(grid): n=len(grid); m=len(grid[0]) rowpref=[[0]*(m+1) for _ in range(n)] colpref=[[0]*(n+1) for _ in range(m)] for i in range(n): for j in range(m): rowpref[i][j+1]=rowpref[i][j]+(grid[i][j] in '34') for j in range(m): for i in range(n): colpref[j][i+1]=colpref[j][i]+(grid[i][j] in '34') neg=-10**9 dp=[[neg]*m for _ in range(n)] dp[0][0]=1 if grid[0][0] in '24' else 0 for i in range(n): for j in range(m): p=dp[i][j] if p<0: continue k=(i+1)+(j+1)-p if i+1<n: R=(j+1)+k-1 cnt=rowpref[i][m]-rowpref[i][min(m,R)] if cnt<=p: np=p+(grid[i+1][j] in '24') dp[i+1][j]=max(dp[i+1][j],np) if j+1<m: B=(i+1)+k-1 cnt=colpref[j][n]-colpref[j][min(n,B)] if cnt<=p: np=p+(grid[i][j+1] in '24') dp[i][j+1]=max(dp[i][j+1],np) return dp[-1][-1]>=0, dp for g in [ ["122","312","321"], ["233","312","321"], ["223","312","321"]]: print(solve(g)[0], solve(g)[1])
pythonfrom itertools import product, combinations def path_feasible_full(grid, moves): n=len(grid); m=len(grid[0]) i=j=0;p=0;k=1 cleaned=[[grid[x][y] in '12' for y in range(m)] for x in range(n)] # track p at row exit and col exit and positions row_exit=[None]*n col_exit=[None]*m path=[] def visit(i,j): nonlocal p,k if grid[i][j] in '13': k+=1 else: p+=1 for x in range(i,min(n,i+k)): for y in range(j,min(m,j+k)): cleaned[x][y]=True path.append((i,j,k,p)) visit(0,0) for mv in moves: if mv=='D': row_exit[i]=(j,p) i+=1 else: col_exit[j]=(i,p) j+=1 visit(i,j) # remaining dirty can only row or col based eligibility; brute assignment/matching capacities? rem=[] for x in range(n): for y in range(m): if not cleaned[x][y]: rem.append((x,y)) # Since any remaining cell either row right of row path or col below col path, possibly both? For monotone path no both. rowcnt=[0]*n; colcnt=[0]*m for x,y in rem: re=row_exit[x] ce=col_exit[y] rowok=re is not None and y>=re[0] # laser suffix incl position colok=ce is not None and x>=ce[0] # Actually path cells cleaned, rem strict if rowok and not colok: rowcnt[x]+=1 elif colok and not rowok: colcnt[y]+=1 elif rowok and colok: print("both?",x,y,moves) return None else: return False for x,c in enumerate(rowcnt): if c and c>row_exit[x][1]: return False for y,c in enumerate(colcnt): if c and c>col_exit[y][1]: return False return True def all_paths(n,m): from itertools import combinations L=n+m-2 for ds in combinations(range(L),n-1): st=set(ds) yield ''.join('D' if x in st else 'R' for x in range(L)) def dp_solve(grid): return solve(grid)[0] for n in range(1,5): for m in range(1,5): for _ in range(1000): import random g=[''.join(str(random.randint(1,4)) for _ in range(m)) for __ in range(n)] brute=any(path_feasible_full(g,p) for p in all_paths(n,m)) d=dp_solve(g) if brute!=d: print("mismatch",n,m,g,brute,d) raise SystemExit print("all ok")
Пусть робот находится в клетке , уже получив увеличение параметра от этой клетки.
До неё он посетил ровно клеток. Каждая посещённая клетка увеличила либо , либо , поэтому
то есть
Значит, если известно текущее значение , значение восстанавливается однозначно.
Пусть из он идёт вниз. После этого в строку он уже никогда не вернётся.
Текущая щётка очищает в этой строке клетки до столбца
Более ранние положения робота не могли очистить клетки правее: правый край квадрата щётки вдоль пути только увеличивается. Будущие положения находятся ниже и уже не могут очистить строку .
Следовательно, все изначально грязные клетки строки , находящиеся правее , необходимо очистить лазером строки. Это возможно тогда и только тогда, когда их количество не превосходит .
Аналогично, при ходе вправо робот навсегда покидает столбец . Число грязных клеток ниже строки должно быть не больше .
Для одной клетки могут существовать разные пути с разными значениями .
Рассмотрим проверку выхода из строки. При увеличении на единицу размер щётки уменьшается на единицу. Поэтому необработанный хвост строки увеличивается максимум на одну клетку, но и мощность лазера увеличивается на единицу.
Таким образом, если переход был возможен при некотором , то он возможен и при любом большем .
То же верно для выхода из столбца. Поэтому для каждой клетки достаточно хранить только максимальное достижимое значение .
Получается обычная динамика по монотонным путям.
cpp#include <algorithm> #include <cstdint> #include <cstdio> #include <string> #include <vector> class FastInput { static constexpr int BUFFER_SIZE = 1 << 20; char buffer_[BUFFER_SIZE]; int position_ = 0; int length_ = 0; inline char getChar() { if (position_ == length_) { length_ = static_cast<int>( std::fread(buffer_, 1, BUFFER_SIZE, stdin) ); position_ = 0; if (length_ == 0) { return '\0'; } } return buffer_[position_++]; } public: int readInt() { char c = getChar(); while (c <= ' ') { c = getChar(); } int value = 0; while (c >= '0' && c <= '9') { value = value * 10 + (c - '0'); c = getChar(); } return value; } void readRow(char* destination, int length) { char c = getChar(); while (c <= ' ') { c = getChar(); } destination[0] = c; for (int i = 1; i < length; ++i) { destination[i] = getChar(); } } }; static inline bool isDirty(char cell) { return cell == '3' || cell == '4'; } static inline int increasesPower(char cell) { return cell == '2' || cell == '4'; } int main() { FastInput input; const int testCount = input.readInt(); std::string output; output.reserve(static_cast<std::size_t>(testCount) * 4); for (int test = 0; test < testCount; ++test) { const int n = input.readInt(); const int m = input.readInt(); const int cellCount = n * m; std::vector<char> grid(cellCount); for (int i = 0; i < n; ++i) { input.readRow(grid.data() + i * m, m); } /* * columnSuffix[i * m + j] — * число изначально грязных клеток в столбце j * на строках i, i + 1, ..., n - 1. * * Максимальное значение равно n <= 3000, * поэтому uint16_t достаточно. */ std::vector<std::uint16_t> columnSuffix(cellCount); std::vector<std::uint16_t> columnCount(m, 0); for (int i = n - 1; i >= 0; --i) { const int rowBase = i * m; for (int j = 0; j < m; ++j) { columnCount[j] += static_cast<std::uint16_t>( isDirty(grid[rowBase + j]) ); columnSuffix[rowBase + j] = columnCount[j]; } } /* * current[j] — максимальная мощность p, * с которой можно оказаться в текущей строке * в столбце j. */ std::vector<int> current(m, -1); std::vector<int> next(m, -1); // Счётчики грязных клеток в суффиксах текущей строки. std::vector<std::uint16_t> rowSuffix(m + 1, 0); // Эффект клетки (1, 1) применяется сразу. current[0] = increasesPower(grid[0]); for (int i = 0; i < n; ++i) { std::fill(next.begin(), next.end(), -1); const int rowBase = i * m; rowSuffix[m] = 0; for (int j = m - 1; j >= 0; --j) { rowSuffix[j] = static_cast<std::uint16_t>( rowSuffix[j + 1] + isDirty(grid[rowBase + j]) ); } for (int j = 0; j < m; ++j) { const int power = current[j]; if (power < 0) { continue; } /* * В нулевой индексации посещено i + j + 1 клеток: * * k = 1 + количество клеток, увеличивших k * = 1 + (i + j + 1 - p) * = i + j + 2 - p. */ const int brushSize = i + j + 2 - power; /* * Ход вправо. * * Мы навсегда покидаем столбец j. * Щётка доходит до строки * bottom = i + brushSize - 1. */ if (j + 1 < m) { const int bottom = i + brushSize - 1; int dirtyBelow = 0; if (bottom + 1 < n) { dirtyBelow = columnSuffix[(bottom + 1) * m + j]; } if (dirtyBelow <= power) { const int newPower = power + increasesPower(grid[rowBase + j + 1]); if (newPower > current[j + 1]) { current[j + 1] = newPower; } } } /* * Ход вниз. * * Мы навсегда покидаем строку i. * Щётка доходит до столбца * right = j + brushSize - 1. */ if (i + 1 < n) { const int right = j + brushSize - 1; const int firstUncovered = std::min(m, right + 1); const int dirtyRight = rowSuffix[firstUncovered]; if (dirtyRight <= power) { const int newPower = power + increasesPower( grid[rowBase + m + j] ); if (newPower > next[j]) { next[j] = newPower; } } } } if (i + 1 < n) { current.swap(next); } } output += current[m - 1] >= 0 ? "Yes\n" : "No\n"; } std::fwrite( output.data(), 1, output.size(), stdout ); return 0; }
Для каждого теста:
по времени и
по памяти. Основной массив суффиксов столбцов занимает байт.