Стоимость скобочной последовательности ограничение...
Created on: June 9, 2026
Created on: June 9, 2026
Стоимость скобочной последовательности
ограничение по времени на тест
2 секунды
ограничение по памяти на тест
512 мегабайт
Пусть стоимость произвольной скобочной строки — это длина её максимальной подпоследовательности∗, которая является правильной скобочной последовательностью†.
Дана скобочная строка s и целое число k. Ваша задача удалить из строки s не более k символов, таким образом, чтобы стоимость получившейся строки была минимальной.
∗Последовательность a является подпоследовательностью b, если a может быть получена из b удалением нескольких (возможно, ни одного или всех) элементов на произвольных позициях.
†Скобочная последовательность называется правильной, если в неё можно вставить символы + и 1 так, чтобы получить корректное арифметическое выражение. Например, последовательности «(())()», «()» и «(()(()))» являются правильными, а «)(», «(()» и «(()))(» — нет.
Входные данные
Каждый тест состоит из нескольких наборов входных данных. В первой строке находится одно целое число t (1≤t≤103) — количество наборов входных данных. Далее следует описание наборов входных данных.
В первой строке каждого набора входных данных содержатся два целых числа n и k (1≤n≤5000; 0≤k≤n) — длина строки s и максимальное количество удалений.
Во второй строке каждого набора входных данных содержится s длины n и состоящая из символов «(» и «)».
Дополнительные ограничения на входные данные:
сумма n по всем наборам входных данных не превосходит 5000.
Выходные данные
Для каждого набора входных данных выведите бинарную строку длины n. i-й символ должен быть равен «1», если соответствующий символ строки s удаляется, и «0» иначе.
Количество единиц в строке не должно превышать k. Стоимость строки, полученной после удаления отмеченных символов, должна быть минимально возможной.
Если существует несколько ответов, выведите любой.
#include <iostream>
#include <vector>
using namespace std;
int main() {
ios_base::sync_with_stdio(0);
cin.tie(0);
textint t; cin >> t; while (t--) { int n, k; cin >> n >> k; string s; cin >> s; int ko = 0, kz = 0; for (int i = 0; i < n; ++i) { if (s[i] == '(') ko++; else kz++; } vector<int> ans(n); if (ko <= kz && k != 0) { int b = 0; for (int i = 0; i < n; ++i) { if (s[i] == '(') { b++; if (b > 0) { ans[i] = 1; k--; } } else b--; if (k == 0) break; } } else if (k != 0) { int b = 0; for (int i = 0; i < n; ++i) { if (s[i] == ')') { b--; if (b >= 0) { ans[i] = 1; k--; } } else b++; if (k == 0) break; } } for (auto u : ans) cout << u; cout << '\n'; } return 0;
}
намекни как мне минимально изменяя код сделать его верным, и потом исправь его МИНИМАЛЬНО его изменяя и все подробно объясняя
Намёк: не надо решать, удалять ( или ) по их общему количеству. Важно не сколько всего скобок каждого типа, а какие из них образуют покрытие всех возможных пар ( перед ).
Текущий код ломается, например, на:
text1 6 1 ))((()
Оптимально удалить последнюю ), тогда стоимость станет 0.
А твой код при ko <= kz удалит первую (, получится:
text))(()
и там всё ещё есть подпоследовательность "()", то есть стоимость 2.
Стоимость строки — это:
text2 * количество пар, которые можно сопоставить
Пару можно сделать, если есть ( слева и ) справа.
Рассмотрим некоторую границу split.
Возьмём множество:
textвсе '(' слева от границы + все ')' справа от границы
Такое множество пересекает любую возможную пару ( ... ).
Если удалить все эти символы, пар точно не останется.
Нам надо найти границу, где это множество минимально.
Для границы после позиции i размер множества равен:
textколичество '(' в префиксе + количество ')' в суффиксе
Это можно переписать как:
texttotal_close + balance
где:
cppbalance = открывающие_в_префиксе - закрывающие_в_префиксе
Значит надо найти позицию, где balance минимален.
Я оставил структуру твоего кода почти такой же: чтение, ans, вывод. Заменена только неправильная логика выбора удаляемых символов.
cpp#include <iostream> #include <vector> using namespace std; int main() { ios_base::sync_with_stdio(0); cin.tie(0); int t; cin >> t; while (t--) { int n, k; cin >> n >> k; string s; cin >> s; int ko = 0, kz = 0; for (int i = 0; i < n; ++i) { if (s[i] == '(') ko++; else kz++; } vector<int> ans(n); int b = 0; int mn = 0; int split = -1; for (int i = 0; i < n; ++i) { if (s[i] == '(') b++; else b--; if (b < mn) { mn = b; split = i; } } for (int i = 0; i < n && k > 0; ++i) { if (i <= split && s[i] == '(') { ans[i] = 1; k--; } else if (i > split && s[i] == ')') { ans[i] = 1; k--; } } for (auto u : ans) cout << u; cout << '\n'; } return 0; }
Вместо этого блока:
cppif (ko <= kz && k != 0) { ... } else if (k != 0) { ... }
мы теперь делаем так:
cppint b = 0; int mn = 0; int split = -1; for (int i = 0; i < n; ++i) { if (s[i] == '(') b++; else b--; if (b < mn) { mn = b; split = i; } }
Здесь split — позиция, после которой выгодно провести границу.
Потом удаляем символы из множества:
cppi <= split && s[i] == '('
или
cppi > split && s[i] == ')'
То есть:
text'(' слева от границы ')' справа от границы
Любая правильная пара имеет вид:
text'(' раньше, ')' позже
Если ( стоит слева от split, мы можем покрыть пару удалением этой (.
Если ( стоит справа от split, то соответствующая ) тоже обязательно справа от split, значит пару можно покрыть удалением этой ).
То есть выбранное множество символов пересекает каждую возможную пару.
Если удалить один символ из такого множества, максимальное число пар уменьшится ровно на 1, пока пары ещё есть. Поэтому можно просто удалить первые k подходящих символов.
Если k больше, чем нужно, удалятся не все k, а только полезные символы. Это разрешено, потому что по условию надо удалить не более k символов.
Ограничение времени: 2 с
Ограничение реального времени: 5 с
Ограничение памяти: 64M
Задача J: Разреженные таблицы
Условие задачи (pdf)
Дан массив из n чисел. Требуется написать программу, которая будет отвечать на запросы следующего вида: найти минимум на отрезке между u и v включительно.
Формат входных данных
В первой строке входного файла даны три натуральных числа n, m (1⩽n⩽105, 1⩽m⩽107) и a1 (0⩽a1<16714589) — количество элементов в массиве, количество запросов и первый элемент массива соответственно. Вторая строка содержит два натуральных числа u1 и v1 (1⩽u1,v1⩽n) — первый запрос.
Элементы a2,a3,…,an задаются следующей формулой:ai+1=(23⋅ai+21563)mod16714589.
Например, при n=10, a1=12345 получается следующий массив: a = (12345, 305498, 7048017, 11694653, 1565158, 2591019, 9471233, 570265, 13137658, 1325095).
Запросы генерируются следующим образом:
ui+1=((17⋅ui+751+ansi+2i)modn)+1,
vi+1=((13⋅vi+593+ansi+5i)modn)+1,
где ansi — ответ на запрос номер i.
Обратите внимание, что ui может быть больше, чем vi.
Формат выходных данных
В выходной файл выведите um, vm и ansm (последний запрос и ответ на него).
#include <iostream>
#include <vector>
using namespace std;
const int mod = 16714589;
vector<long long> a, logs;
vector<vector<long long>> table;
long long get(int l, int r) {
int len = r - l + 1;
int k = logs[len];
return min(table[k][l], table[k][r - (1 << k) + 1]);
}
int main() {
ios_base::sync_with_stdio(0);
cin.tie(0);
textint n, m, a1; cin >> n >> m >> a1; a.resize(n); a[0] = a1; for (int i = 1; i < n; ++i) { a[i] = (a[i - 1] * 23 + 21563) % mod; } logs.resize(100002); logs[1] = 0; for (int i = 2; i <= 100000; ++i) { logs[i] = logs[i / 2] + 1; } int lg = logs[n]; table.resize(lg + 1, vector<long long> (n)); for (int i = 0; i < n; ++i) table[0][i] = a[i]; for (int k = 1; k <= lg; ++k) { for (int j = 0; j <= n - (1 << k); ++j) { table[k][j] = min(table[k - 1][j], table[k - 1][j + (1 << (k - 1))]); } } int u1, v1; cin >> u1 >> v1; vector<int> u(m + 1), v(m + 1); u[0] = u1; v[0] = v1; for (int i = 0; i < m; ++i) { int l = min(u[i], v[i]) - 1; int r = max(u[i], v[i]) - 1; long long ans = get(l, r); u[i + 1] = ((((17LL * u[i] + 751 + ans + 2LL * (i + 1)) % n) + n) % n) + 1; v[i + 1] = ((((13LL * v[i] + 593 + ans + 5LL * (i + 1)) % n) + n) % n) + 1; if (i == m - 1) { cout << u[i] << ' ' << v[i] << ' ' << ans; } } return 0;
}
что не так? исправь мой код, МИНИМАЛЬНО его изменяя и ОЧЕНБ ПОДРОБНО все объясняя
Главная ошибка: ты хранишь все u и v:
cppvector<int> u(m + 1), v(m + 1);
А m до 10^7.
Это примерно:
textu: 40 МБ v: 40 МБ
Уже 80 МБ, а лимит — 64 МБ. Поэтому будет превышение памяти.
Нужно хранить только текущие u, v, потому что каждый следующий запрос зависит только от предыдущего.
Ещё лучше заменить long long в таблице на int, потому что элементы массива меньше 16714589.
Минимально исправленный код:
cpp#include <iostream> #include <vector> using namespace std; const int mod = 16714589; vector<int> a, logs; vector<vector<int>> table; int get(int l, int r) { int len = r - l + 1; int k = logs[len]; return min(table[k][l], table[k][r - (1 << k) + 1]); } int main() { ios_base::sync_with_stdio(0); cin.tie(0); int n, m, a1; cin >> n >> m >> a1; a.resize(n); a[0] = a1; for (int i = 1; i < n; ++i) { a[i] = (1LL * a[i - 1] * 23 + 21563) % mod; } logs.resize(n + 1); logs[1] = 0; for (int i = 2; i <= n; ++i) { logs[i] = logs[i / 2] + 1; } int lg = logs[n]; table.resize(lg + 1, vector<int>(n)); for (int i = 0; i < n; ++i) table[0][i] = a[i]; for (int k = 1; k <= lg; ++k) { for (int j = 0; j <= n - (1 << k); ++j) { table[k][j] = min(table[k - 1][j], table[k - 1][j + (1 << (k - 1))]); } } int u, v; cin >> u >> v; int ans = 0; for (int i = 1; i <= m; ++i) { int l = min(u, v) - 1; int r = max(u, v) - 1; ans = get(l, r); if (i == m) { cout << u << ' ' << v << ' ' << ans; break; } int nu = ((17LL * u + 751 + ans + 2LL * i) % n) + 1; int nv = ((13LL * v + 593 + ans + 5LL * i) % n) + 1; u = nu; v = nv; } return 0; }
Что изменилось минимально:
cppvector<long long> a, logs; vector<vector<long long>> table;
заменили на:
cppvector<int> a, logs; vector<vector<int>> table;
Потому что значения маленькие, long long не нужен.
Было:
cppvector<int> u(m + 1), v(m + 1);
Стало:
cppint u, v;
Потому что нам не нужны все запросы, нужен только текущий.
Цикл стал таким:
cppfor (int i = 1; i <= m; ++i)
Потому что формулы используют номер запроса i, начиная с 1.
И важно: сначала считаем ответ на текущий запрос, потом, если это не последний запрос, генерируем следующий.
D. Совершенно новое татарское телешоу
ограничение по времени на тест
2 секунды
ограничение по памяти на тест
256 мегабайт
Дабиру и Егору было недостаточно славы с предыдущего выпуска, что они решили снова устроить телешоу: ребята играют в свою любимую игру на массиве a и с их любимым числом k.
Дабир ходит первым. Первым ходом выбирается любое число из массива и удаляется. Если на предыдущем ходу был выбран элемент равный x, то на текущем ходу игрок должен выбрать из оставшихся такой элемент y что 0≤y−x≤k и он будет удалён. Игрок, который не может сделать ход проигрывает.
Но так как это была не простая игра, а настоящий шоу-матч, то на игру вновь пригласили Арсения (aka MAKAN) — главную знаменитость Омска. На правах приглашённой знаменитости Арсению дали возможность сделать первый ход в этом матче, то есть сделать самый первый ход в игре вместо Дабира. Но оказалось, что Арсений поклонник Егора, а потому хочет сделать первый ход такой, чтобы у Егора была выигрышная стратегия при любой игре Дабира.
Определите, может ли Арсений сделать первый ход за Дабира так, чтобы при любых ходах Дабира победил Егор.
Входные данные
Каждый тест состоит из нескольких наборов входных данных. В первой строке вводится одно целое число t (1≤t≤104) — количество наборов входных данных.
Первая строка каждого набора входных данных содержит одно целое число n и k (1≤n,k≤2⋅105) — длину массива a и любимое число Дабира и Егора.
Вторая строка содержит n целых чисел a1,a2,…,an (1≤ai≤n).
Гарантируется, что сумма значений n по всем наборам входных данных не превышает 2⋅105.
Выходные данные
Для каждого набора входных данных, если существует такой первый ход, что при дальнейшей оптимальной игре обоих игроков Егор победит, то выведите «YES», иначе выведите «NO».
Вы можете выводить «YES» и «NO» в любом регистре (например, строки «yES»,«yes» и «Yes» будут распознаны как правильный ответ).
#include <iostream>
#include <vector>
#include <set>
#include <map>
using namespace std;
int main() {
ios_base::sync_with_stdio(0);
cin.tie(0);
textint t; cin >> t; while (t--) { int n, k; cin >> n >> k; set<int> d; vector<int> a(n); for (int i = 0; i < n; ++i) { cin >> a[i]; d.insert(a[i]); } map<int, int> m; for (int i = 0; i < n; ++i) { m[a[i]]++; } vector<int> s; for (auto u : d) s.push_back(u); reverse(s.begin(), s.end()); vector<pair<int, int>> v(n); int id = 0; int pr = -1; for (auto u : s) { if (pr == -1 || pr - u > k) { v[id].first = u; id++; } else if (v[id - 1].second == 0) v[id - 1].second = u; } bool fl = false; for (int i = 0; i < id; ++i) { int m1 = v[i].first, m2 = v[i].second; if (m[m1] % 2 == 0) { cout << "YES\n"; fl = true; break; } else if (m2 != 0 && m1 - m2 <= k) { cout << "YES\n"; fl = true; break; } } if (!fl) cout << "NO\n"; } return 0;
}
C. Омские программисты
ограничение по времени на тест
1 секунда
ограничение по памяти на тест
256 мегабайт
На главной площади Омска проходит ежегодное мероприятие — ярмарка программистов. Вы, как главный программист Омска, решили поучаствовать в данном прекрасном мероприятии и выдвинулись туда. На входе охранник решил проверить ваши навыки и задал вам задачу:
У вас есть три целых числа a, b, x. Вы хотите сделать a и b равными. Для этого вы можете применять следующие операции:
Выбрать одно из чисел a или b и прибавить к нему 1.
Выбрать одно из чисел a или b и разделить его с округлением вниз на x.
Необходимо найти минимальное количество операций, после которых a станет равным b. Сможете ли вы доказать свои навыки, или вам придется поехать домой?
Входные данные
В первой строке записано одно целое число t (1≤t≤104) — количество наборов входных данных.
Далее следуют t наборов входных данных.
В единственной строке каждого набора данных дано три целых числа a, b, x (1≤a,b≤109, 2≤x≤109).
Выходные данные
Для каждого набора входных данных выведите целое число, обозначающее минимальное количество операций, необходимых, чтобы сделать a и b равными.
#include <iostream> using namespace std; int main() { ios_base::sync_with_stdio(0); cin.tie(0); int t; cin >> t; while (t--) { int a, b, x; cin >> a >> b >> x; int ans = 0; while (a != b) { if (a > b) swap(a, b); int k = b - a; int r1 = b / x - a / x + 2; int r2; if (b / x < a) r2 = a - b / x + 1; else r2 = b / x - a + 1; if (k <= r1 && k <= r2) { ans += k; break; } if (r1 < r2) { ans += 2; b /= x; a /= x; } else { ans++; b /= x; } } cout << ans << '\n'; } return 0; }
что не так? исправь мои коды МИНИМАЛЬНО их изменяя и ОЧЕНЬ ПОДРОБНО ОБЪЯСНЯЯ все исправления и мои ошибки
Разберу обе задачи.
Твой код пытается делить значения на группы и смотреть только:
cppm[m1] % 2
и наличие второго элемента в группе.
Но игра устроена не так. Важно не просто количество максимального элемента, а есть ли дальше проигрышная позиция, в которую можно сходить.
Ещё в твоём коде есть техническая ошибка:
cppint pr = -1; for (auto u : s) { if (pr == -1 || pr - u > k) { ... } }
Ты нигде не делаешь:
cpppr = u;
То есть pr всегда остаётся -1, и группировка вообще не работает.
Пусть есть некоторое значение x.
После того как Арсений выбрал x, следующий ход делает Егор.
Егор может брать:
texty >= x и y - x <= k
То есть игра всегда идёт вправо по значениям, назад вернуться нельзя.
Для каждого различного значения x посчитаем:
cpplose[x]
Это значит: если предыдущим ходом уже взяли x, и сейчас ход текущего игрока, то текущий игрок проигрывает.
Если количество x нечётное, то после первого взятия x остаётся чётное число таких же x.
Если из x нельзя перейти в уже проигрышную позицию, то позиция проигрышная.
Формула такая:
cpplose[i] = cnt[i] % 2 == 1 && нет проигрышного значения справа на расстоянии <= k
Почему cnt[i] % 2 == 1?
Потому что Арсений уже взял один элемент x.
Осталось:
cppcnt[x] - 1
Если cnt[x] нечётное, то cnt[x] - 1 чётное. Тогда игра на одинаковых x сама по себе проигрышная, если нет хорошего перехода вправо.
Я оставил твои set, map, vector, но полностью заменил неправильную логику группировки.
cpp#include <iostream> #include <vector> #include <set> #include <map> #include <algorithm> using namespace std; int main() { ios_base::sync_with_stdio(0); cin.tie(0); int t; cin >> t; while (t--) { int n, k; cin >> n >> k; set<int> d; vector<int> a(n); for (int i = 0; i < n; ++i) { cin >> a[i]; d.insert(a[i]); } map<int, int> m; for (int i = 0; i < n; ++i) { m[a[i]]++; } vector<int> s; for (auto u : d) s.push_back(u); set<int> lose_values; bool fl = false; for (int i = (int)s.size() - 1; i >= 0; --i) { int x = s[i]; auto it = lose_values.upper_bound(x); bool has_lose = false; if (it != lose_values.end() && *it - x <= k) { has_lose = true; } bool lose = (m[x] % 2 == 1 && !has_lose); if (!lose) { fl = true; } else { lose_values.insert(x); } } if (fl) cout << "YES\n"; else cout << "NO\n"; } return 0; }
Было:
cppvector<pair<int, int>> v(n); int id = 0; int pr = -1; ...
Это удалено, потому что такая группировка не описывает игру.
Добавили:
cppset<int> lose_values;
Тут храним значения, из которых следующий игрок проигрывает.
Проверяем, есть ли проигрышное значение справа:
cppauto it = lose_values.upper_bound(x); bool has_lose = false; if (it != lose_values.end() && *it - x <= k) { has_lose = true; }
Если есть проигрышное значение справа на расстоянии <= k, значит текущая позиция выигрышная: можно сходить туда.
Считаем:
cppbool lose = (m[x] % 2 == 1 && !has_lose);
Если позиция не проигрышная, значит Арсений может выбрать это значение, и Егор будет выигрывать:
cppif (!lose) { fl = true; }
Твой код жадный:
cppif (r1 < r2) { ans += 2; b /= x; a /= x; } else { ans++; b /= x; }
Но локально лучший ход не всегда ведёт к глобальному минимуму.
Тут надо перебрать, сколько раз мы делим a, и сколько раз делим b.
После этого меньшее число можно догнать операциями +1.
То есть вариант такой:
textподелили a i раз поделили b j раз потом прибавили разницу
Цена:
cppi + j + abs(a_after - b_after)
Так как a, b <= 1e9, делений будет мало: максимум около 31 при x = 2.
cpp#include <iostream> #include <vector> #include <cmath> using namespace std; int main() { ios_base::sync_with_stdio(0); cin.tie(0); int t; cin >> t; while (t--) { long long a, b, x; cin >> a >> b >> x; vector<long long> va, vb; long long cur = a; while (true) { va.push_back(cur); if (cur == 0) break; cur /= x; } cur = b; while (true) { vb.push_back(cur); if (cur == 0) break; cur /= x; } long long ans = abs(a - b); for (int i = 0; i < (int)va.size(); ++i) { for (int j = 0; j < (int)vb.size(); ++j) { ans = min(ans, 1LL * i + j + abs(va[i] - vb[j])); } } cout << ans << '\n'; } return 0; }
Например, для числа a строим все варианты после делений:
cppvector<long long> va; long long cur = a; while (true) { va.push_back(cur); if (cur == 0) break; cur /= x; }
То есть если:
texta = 20, x = 3
то получим:
text20, 6, 2, 0
Индексы в этом массиве — это количество операций деления.
Например:
cppva[2] = 2
значит:
text20 / 3 / 3 = 2
и это стоит 2 операции.
Потом перебираем:
cppfor (int i = 0; i < va.size(); ++i) { for (int j = 0; j < vb.size(); ++j) { ans = min(ans, i + j + abs(va[i] - vb[j])); } }
Смысл:
texti делений сделали с a j делений сделали с b потом меньшее число увеличили до большего
Количество +1 операций равно:
cppabs(va[i] - vb[j])
Итого:
cppi + j + abs(va[i] - vb[j])
Даже если a = 1e9, а x = 2, цепочка делений короткая:
text1e9, 5e8, 2.5e8, ..., 0
Это примерно 31 значение.
Значит на один тест:
text31 * 31
операций, что очень мало.