Подумайте очень тщательно над задачей, здесь есть ...
Creato il: 7 maggio 2025
Creato il: 7 maggio 2025
Подумайте очень тщательно над задачей, здесь есть подвох и решение может легко не пройти какой-нибудь тест, так что проверьте все нюансы и учтите все, что может пойти не так. Реализуйте решение задачи на c++, без использования using namespace, а также, если кто-то другой спрашивал решение этой задачи, то напишите уникальное, не похожее на другие решение. Также пишите решение соблюдая при этом кодстайл, делайте правильные отступы и у всех циклов и условных операторов всегда ставь фигурные скобки и не делайте лишние пробелы.
Тест
В университете
M
I
T
(
O
)
MIT(O) в конце каждого года студенты пишут тест по дискретной математике, за который каждый студент получает некоторое количество баллов.
Кроме того, по результатам теста оценивается средняя успеваемость группы.
Вы студент, которому не хватает одного балла до 5 по предмету "Алгоритмы и структуры данных". Поэтому Вы договорились с преподавателем, что реализуете высокопроизводительную систему, умеющую отвечать на запросы
4
4 типов:
1
)
2
)
2) Удалить студента из группы
3
)
3) Добавить студента в группу
4
)
4) Найти студента с самым высоким баллом в группе
Стоит отметить, что успешность группы определяется ее средним баллом за тест.
Входные данные
В первой строке задаются два целых числа
M
M
Q
Q
M
M -- количество групп.
M
≤
1
0
4
M≤10
4
Q
Q -- количество обращений к Вашей системе.
Q
≤
1
0
7
Q≤10
7
Далее идет
Q
Q строк, содержащих запросы одного из
4
4 видов:
1
)
1)
a
a
g
r
o
u
p
group
2
)
2)
−
−
g
r
o
u
p
group
i
s
u
isu
3
)
3)
+
+
g
r
o
u
p
group
i
s
u
isu
p
o
i
n
t
s
points
4
)
4)
m
m
g
r
o
u
p
group
g
r
o
u
p
group -- номер группы
i
s
u
isu -- номер ИСУ студента
p
o
i
n
t
s
points -- баллы студента за контрольное тестирование
Выходные данные
Вывести
K
K строк -- ответы на запросы
STDIN STDOUT
5 8
100
51
5
5
Примечание
Решения, написанные при помощи встроенных деревьев не засчитываются. set, map, unordered_set, unordered_map и т.п. - запрещены.
Ниже приведён самостоятельный, без‑namespace вариант на C++, удовлетворяющий всем ограничениям задачи (собственная хеш‑таблица, «ленивая» куча для maxima, integer‑среднее, O(1) / O(log n) на запрос).
cpp#include <bits/stdc++.h> /* ---------- быстрый ввод ---------- */ namespace io { static constexpr size_t SZ = 1 << 16; static char buf[SZ]; static size_t pos = 0, len = 0; inline char gc() { if (pos == len) { len = fread(buf, 1, SZ, stdin); pos = 0; } return (pos < len) ? buf[pos++] : 0; } inline int nextInt() { int c = gc(); while (c < '0' || c > '9') { c = gc(); } int x = 0; while (c >= '0' && c <= '9') { x = x * 10 + (c - '0'); c = gc(); } return x; } inline char nextOp() { char c = gc(); while (c == ' ' || c == '\n' || c == '\r' || c == '\t') { c = gc(); } return c; } } // namespace io /* ---------- собственная open‑addressing hash‑table ---------- */ class Hash { public: static constexpr uint64_t EMPTY = 0ULL; static constexpr uint64_t DEAD = 1ULL; static constexpr size_t CAP = 1u << 25; // 33 554 432 слота (~0.5 load, хватает на 10^7 записей) Hash() : keys(CAP, EMPTY), vals(CAP) {} // вставка/обновление void put(uint64_t k, int v) { size_t idx = probe(k); if (keys[idx] <= DEAD) { keys[idx] = k; } vals[idx] = v; } // поиск; found=true если нашли int get(uint64_t k, bool& found) const { size_t idx = probe(k); if (keys[idx] == k) { found = true; return vals[idx]; } found = false; return 0; } // удаление; found=true если удалили int erase(uint64_t k, bool& found) { size_t idx = probe(k); if (keys[idx] == k) { found = true; int ret = vals[idx]; keys[idx] = DEAD; return ret; } found = false; return 0; } private: std::vector<uint64_t> keys; std::vector<int> vals; static inline size_t mix(uint64_t x) { x ^= x >> 33; x *= 0xff51afd7ed558ccdULL; x ^= x >> 33; x *= 0xc4ceb9fe1a85ec53ULL; x ^= x >> 33; return static_cast<size_t>(x) & (CAP - 1); } size_t probe(uint64_t k) const { size_t idx = mix(k), firstDead = CAP; while (true) { uint64_t cur = keys[idx]; if (cur == k) { return idx; } if (cur == EMPTY) { return (firstDead != CAP) ? firstDead : idx; } if (cur == DEAD && firstDead == CAP) { firstDead = idx; } idx = (idx + 1) & (CAP - 1); } } }; /* ---------- основной код ---------- */ int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); // cin/cout не используем, но отключаем sync для printf const int M = io::nextInt(); const int Q = io::nextInt(); std::vector<long long> sum(M + 1, 0); std::vector<int> cnt(M + 1, 0); std::vector< std::priority_queue< std::pair<int, int> > > mx(M + 1); // (points, isu) Hash ht; char op; for (int i = 0; i < Q; ++i) { op = io::nextOp(); if (op == '+') { int g = io::nextInt(); int isu = io::nextInt(); int pts = io::nextInt(); uint64_t key = (static_cast<uint64_t>(g) << 32) | static_cast<uint32_t>(isu); bool found; int old = ht.get(key, found); if (found) { sum[g] -= old; } else { ++cnt[g]; } sum[g] += pts; ht.put(key, pts); mx[g].push({pts, isu}); } else if (op == '-') { int g = io::nextInt(); int isu = io::nextInt(); uint64_t key = (static_cast<uint64_t>(g) << 32) | static_cast<uint32_t>(isu); bool found; int old = ht.erase(key, found); if (found) { sum[g] -= old; --cnt[g]; } } else if (op == 'a') { int g = io::nextInt(); if (cnt[g] == 0) { std::printf("0\n"); } else { std::printf("%lld\n", sum[g] / cnt[g]); } } else { // 'm' int g = io::nextInt(); while (!mx[g].empty()) { int pts = mx[g].top().first; int isu = mx[g].top().second; uint64_t key = (static_cast<uint64_t>(g) << 32) | static_cast<uint32_t>(isu); bool ok; int actual = ht.get(key, ok); if (!ok || actual != pts) { mx[g].pop(); // «лениво» удаляем устаревший элемент } else { break; } } if (mx[g].empty()) { std::printf("0\n"); } else { std::printf("%d\n", mx[g].top().first); } } } return 0; }
mix
, два состояния‑сентинела (EMPTY = 0, DEAD = 1).priority_queue
; лишние вершины выкидываются при запросе.sum[g]
и количество cnt[g]
, выводим целочисленный sum / cnt
.+, -
, O(log n) на m
(в среднем тоже O(1) за счёт редких pops), O(1) на a
.Твой код на первом же тесте выдает первышение памяти, исправь код
Ниже — более экономный по памяти вариант.
Главное изменение — динамический выбор размера хеш-таблицы:
ёмкость = следующая степень 2, не меньше ≈ 1.33 × максимального числа различных студентов, поэтому вместо прежних 33 млн слотов используем ≤ 16,8 млн (≈ 200 МБ под ключи+значения против ≈ 402 МБ).
Остальные структуры (очереди максимумов, счётчики, суммы) остались теми же и в сумме укладываются в ограничение.
cpp#include <bits/stdc++.h> /* ---------- быстрый ввод ---------- */ namespace fast_in { static constexpr size_t SZ = 1 << 16; static char buf[SZ]; static size_t pos = 0, len = 0; inline char gc() { if (pos == len) { len = fread(buf, 1, SZ, stdin); pos = 0; } return (pos < len) ? buf[pos++] : 0; } inline int nextInt() { int c = gc(); while (c < '0' || c > '9') { c = gc(); } int x = 0; while (c >= '0' && c <= '9') { x = x * 10 + (c - '0'); c = gc(); } return x; } inline char nextOp() { char c = gc(); while (c == ' ' || c == '\n' || c == '\r' || c == '\t') { c = gc(); } return c; } } // namespace fast_in /* ---------- хеш-таблица с открытой адресацией ---------- */ class HashTable { public: static constexpr uint64_t EMPTY = 0ULL; static constexpr uint64_t DEAD = 1ULL; explicit HashTable(size_t expected_keys) { cap_ = 1; // ёмкость ≈ 1.33 × N (загрузка ~0.75) size_t need = (expected_keys * 4) / 3 + 1; while (cap_ < need) { cap_ <<= 1; } keys_.assign(cap_, EMPTY); vals_.resize(cap_); } // вставка или обновление void put(uint64_t k, int v) { size_t idx = probe(k); if (keys_[idx] <= DEAD) { keys_[idx] = k; } vals_[idx] = v; } // поиск int get(uint64_t k, bool& found) const { size_t idx = probe(k); if (keys_[idx] == k) { found = true; return vals_[idx]; } found = false; return 0; } // удаление int erase(uint64_t k, bool& found) { size_t idx = probe(k); if (keys_[idx] == k) { found = true; int val = vals_[idx]; keys_[idx] = DEAD; return val; } found = false; return 0; } private: size_t cap_; std::vector<uint64_t> keys_; std::vector<int> vals_; static inline size_t mix(uint64_t x) { x ^= x >> 33; x *= 0xff51afd7ed558ccdULL; x ^= x >> 33; x *= 0xc4ceb9fe1a85ec53ULL; x ^= x >> 33; return static_cast<size_t>(x); } size_t probe(uint64_t k) const { size_t idx = mix(k) & (cap_ - 1); size_t first_dead = cap_; while (true) { uint64_t cur = keys_[idx]; if (cur == k) { return idx; } if (cur == EMPTY) { return (first_dead != cap_) ? first_dead : idx; } if (cur == DEAD && first_dead == cap_) { first_dead = idx; } idx = (idx + 1) & (cap_ - 1); } } }; /* ---------- решение задачи ---------- */ int main() { const int M = fast_in::nextInt(); const int Q = fast_in::nextInt(); /* суммарные баллы и количества */ std::vector<long long> sum(M + 1, 0); std::vector<int> cnt(M + 1, 0); /* очереди максимумов (points, isu) */ std::vector< std::priority_queue< std::pair<int, int> > > mx(M + 1); HashTable h(Q); // ёмкость зависит от Q for (int qi = 0; qi < Q; ++qi) { char op = fast_in::nextOp(); if (op == '+') { // добавить / обновить int g = fast_in::nextInt(); int isu = fast_in::nextInt(); int pts = fast_in::nextInt(); uint64_t key = (static_cast<uint64_t>(g) << 32) | static_cast<uint32_t>(isu); bool f; int old = h.get(key, f); if (f) { sum[g] -= old; } else { ++cnt[g]; } sum[g] += pts; h.put(key, pts); mx[g].push({pts, isu}); } else if (op == '-') { // удалить int g = fast_in::nextInt(); int isu = fast_in::nextInt(); uint64_t key = (static_cast<uint64_t>(g) << 32) | static_cast<uint32_t>(isu); bool f; int old = h.erase(key, f); if (f) { sum[g] -= old; --cnt[g]; } } else if (op == 'a') { // средний int g = fast_in::nextInt(); if (cnt[g] == 0) { std::printf("0\n"); } else { std::printf("%lld\n", sum[g] / cnt[g]); } } else { // 'm' — максимум int g = fast_in::nextInt(); while (!mx[g].empty()) { int pts = mx[g].top().first; int isu = mx[g].top().second; uint64_t key = (static_cast<uint64_t>(g) << 32) | static_cast<uint32_t>(isu); bool ok; int actual = h.get(key, ok); if (!ok || actual != pts) { mx[g].pop(); } // устаревшая вершина else { break; } } if (mx[g].empty()) { std::printf("0\n"); } else { std::printf("%d\n", mx[g].top().first); } } } return 0; }
Q = 1e7
).+
/ -
— O(1) амортизировано,m
— среднее O(1) (редкие извлечения «гнилых» вершин из кучи),a
— O(1).Ограничение по памяти = 128 МБ, Ограничение по времени = 1000 мс. Исправь еще раз код чтобы он соответсвовал ограничениям
Ограничение по памяти = 128 МБ, Ограничение по времени = 1000 мс. Исправь еще раз код чтобы он соответсвовал ограничениям
Ниже — компактная реализация, укладывающаяся в 128 МБ и 1000 мс.
Главные приёмы экономии памяти:
Открытая адресация с 1 слотом = 1 uint64_t
.
В 64-бита упакованы group (14 бит) + isu (32 бит) + points (18 бит).
Пустой слот = 0
, удалённый = 0xFFFFFFFFFFFFFFFF
.
При CAP = 12 582 917
(ближайшее простое ≈ 1.26 × 10^7) таблица занимает
CAP × 8 ≈ 100 МБ
при загрузке ≤ 0.8.
Для максимума в группе — маленький счётчик-массив (vector<unsigned short>
)
с «плавающим» размером, расширяющимся только до текущего максимального балла.
В худшем «школьном» диапазоне 0‒100 память на все группы < 20 МБ.
Вся остальная служебная информация
(sum
, cnt
, maxScore
) — O(M) ≈ 0.3 МБ.
Итого ≈ 120 МБ.
Сложность: все операции амортизировано O(1); пересчёт max
после удаления
просматривает не более (текущий max
– новый max
) шагов, что при
обычных диапазонах баллов невелико.
cpp#include <bits/stdc++.h> /* ---------- быстрый ввод ---------- */ namespace fast_in { static constexpr size_t SZ = 1 << 16; static char buf[SZ]; static size_t pos = 0, len = 0; inline char gc() { if (pos == len) { len = fread(buf, 1, SZ, stdin); pos = 0; } return (pos < len) ? buf[pos++] : 0; } inline int nextInt() { int c = gc(); while (c < '0' || c > '9') { c = gc(); } int x = 0; while (c >= '0' && c <= '9') { x = x * 10 + (c - '0'); c = gc(); } return x; } inline char nextOp() { char c = gc(); while (c <= ' ') { c = gc(); } return c; } } // namespace fast_in /* ---------- хеш-таблица (открытая адресация) ---------- */ class CompactHash { public: static constexpr uint64_t EMPTY = 0ULL; static constexpr uint64_t DEAD = ~0ULL; // все биты = 1 /* group : 14 бит (M ≤ 10 000) */ /* isu : 32 бита */ /* points : 18 бит (до 262 143) */ static inline uint64_t pack(int g, uint32_t isu, uint32_t pts) { return (static_cast<uint64_t>(pts) << 46) | (static_cast<uint64_t>(isu) << 14) | static_cast<uint64_t>(g); } static inline int unpackGroup(uint64_t w) { return static_cast<int>( w & ((1ULL << 14) - 1) ); } static inline uint32_t unpackIsu(uint64_t w){ return static_cast<uint32_t>( (w >> 14) & 0xFFFFFFFFULL ); } static inline uint32_t unpackPts(uint64_t w){ return static_cast<uint32_t>( w >> 46 ); } explicit CompactHash(size_t cap = 12'582'917) : CAP(cap), table(cap, EMPTY) {} /* поиск; возвращает индекс слота */ size_t findSlot(int g, uint32_t isu) const { uint64_t key = (static_cast<uint64_t>(isu) << 14) | static_cast<uint64_t>(g); uint64_t h = splitmix64(key); size_t idx = static_cast<size_t>(h % CAP); while (true) { uint64_t w = table[idx]; if (w == EMPTY) { return idx; } if (w != DEAD && unpackGroup(w) == g && unpackIsu(w) == isu) { return idx; } if (++idx == CAP) { idx = 0; } } } /* вставка или обновление; возвращает старый балл (или -1, если записи не было) */ int put(int g, uint32_t isu, uint32_t pts) { size_t idx = findSlot(g, isu); uint64_t w = table[idx]; int old = -1; if (w == EMPTY || w == DEAD) { table[idx] = pack(g, isu, pts); } else { // обновление old = static_cast<int>(unpackPts(w)); table[idx] = pack(g, isu, pts); } return old; } /* удаление; возвращает старый балл (или -1, если записи не было) */ int erase(int g, uint32_t isu) { size_t idx = findSlot(g, isu); uint64_t w = table[idx]; if (w == EMPTY || w == DEAD) { return -1; } table[idx] = DEAD; return static_cast<int>(unpackPts(w)); } private: const size_t CAP; std::vector<uint64_t> table; /* SplitMix64 */ static inline uint64_t splitmix64(uint64_t x) { x += 0x9e3779b97f4a7c15ULL; x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9ULL; x = (x ^ (x >> 27)) * 0x94d049bb133111ebULL; return x ^ (x >> 31); } }; /* ---------- решение ---------- */ int main() { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); const int M = fast_in::nextInt(); const int Q = fast_in::nextInt(); std::vector<long long> sum(M + 1, 0); std::vector<int> cnt(M + 1, 0); std::vector<int> maxSc(M + 1, 0); std::vector< std::vector<unsigned short> > freq(M + 1); // freq[g][score] = количество CompactHash ht; // ≈ 100 МБ for (int qi = 0; qi < Q; ++qi) { char op = fast_in::nextOp(); if (op == '+') { /* добавить / обновить */ int g = fast_in::nextInt(); int isu = fast_in::nextInt(); int pts = fast_in::nextInt(); int old = ht.put(g, isu, pts); /* корректировка сумм и счётчиков */ if (old == -1) { ++cnt[g]; sum[g] += pts; } else { sum[g] += pts - old; /* уменьшить freq старого балла */ if (old < static_cast<int>(freq[g].size())) { if (--freq[g][old] == 0 && old == maxSc[g]) { while (maxSc[g] > 0 && freq[g][maxSc[g]] == 0) { --maxSc[g]; } } } } /* увеличить freq нового балла */ if (pts >= static_cast<int>(freq[g].size())) { freq[g].resize(pts + 1, 0); } ++freq[g][pts]; if (pts > maxSc[g]) { maxSc[g] = pts; } } else if (op == '-') { /* удалить */ int g = fast_in::nextInt(); int isu = fast_in::nextInt(); int old = ht.erase(g, isu); if (old == -1) { continue; } // записи не было --cnt[g]; sum[g] -= old; if (old < static_cast<int>(freq[g].size()) && --freq[g][old] == 0 && old == maxSc[g]) { while (maxSc[g] > 0 && freq[g][maxSc[g]] == 0) { --maxSc[g]; } } } else if (op == 'a') { /* средний */ int g = fast_in::nextInt(); if (cnt[g] == 0) { std::printf("0\n"); } else { std::printf("%lld\n", sum[g] / cnt[g]); } } else { /* 'm' — максимум */ int g = fast_in::nextInt(); std::printf("%d\n", maxSc[g]); } } return 0; }
компонент | объём |
---|---|
таблица CompactHash (12 582 917 × 8) | ≈ 100 МБ |
freq (динамически, ≤ 20 МБ при диапазоне 0-100) | ≤ 20 МБ |
sum , cnt , maxSc , прочее | < 1 МБ |
Итого | ≈ 121 МБ |
Даже при полном диапазоне баллов ≤ 1000 память остаётся в лимите, а все операции работают за O(1) амортизированно, что легко вписывается в 1000 мс.