245A - System Administrator
Задача на реализацию того, что написано в условии. Код мог выглядеть как-то так:
int[] accepted = new int[2];
int[] lost = new int[2];
for (int i = 0; i < n; i++) {
int z = nextInt() - 1;
accepted[z] += nextInt();
lost[z] += nextInt();
}
for (int i = 0; i < lost.length; i++) {
if (accepted[i] >= lost[i]) {
out.println("LIVE");
} else {
out.println("DEAD");
}
}
245B - Internet Address
В данной задаче требовалось умение работать со строками. Опять же, приведу код одного из прорешивающих:
String s = nextToken();
if (s.startsWith("http")) {
out.print("http://");
s = s.substring(4);
} else {
out.print("ftp://");
s = s.substring(3);
}
out.print(s.substring(0, s.lastIndexOf("ru")));
out.print(".ru");
s = s.substring(s.lastIndexOf("ru") + 2);
if (s.length() != 0) {
out.print("/");
out.println(s);
}
245C - Game with Coins
У этой задачи было несколько решений, видимо, самое простое для осознание использует идею динамического программирования. Динамическое программирование dp[i][up], сколько нужно ходов, чтобы опустошить сундук номер i и все зависящие от него сундуки, при условии, что в нем сейчас находится max(0, ai - up) монет.
Чтобы посчитать dp[i][up] переберем сколько раз мы возьмем монету из этого сундука. Пусть мы возьмем из него p, тогда должно выполняться max(0, ai - up - p) = 0. Перебрав p переходим к подзадачам dp[2·i][p], dp[2·i + 1][p].
После подсчета всех состояний, ответ на задачу будет содержаться в dp[1][0].
Сколько операций выполнит подсчет такого динамического программирования. Очевидно, что p не имеет смысла брать более 1000. Тогда всего состояний в таком dp будет n·1000. Умножить на количество переходов, получается 100·1000·1000 = 108, операций в худшем случае.
245D - Restoring Table
В этой задаче важно было условие, что решение всегда существует. Получить его можно было например так:
for(int i = 0; i < n; ++i)
for(int j = 0; j < n; ++j){
cin >> b[i][j];
if(i == j)
continue;
a[i] |= b[i][j];
}
245E - Mishap in Club
В задаче нужно было применить жадные соображения следующего характера. Если есть человек, который вышел и которого видел Поликарп, то если кто-то заходит в клуб, можно считать, что заходит этот человек. Аналогочно для выходящих. Лаконичный код решения:
int in = 0, out = 0;
for(int i = 0; i < n; ++i){
if(s[i] == '+'){
in++;
out = max(out - 1, 0);
}
if(s[i] == '-'){
out++;
in = max(in - 1, 0);
}
}
cout << in + out << endl;
245F - Log Stream Analysis
В этой задаче нужно было аккуратно распарзить входные данные. Перевести все даты в секунды. А затем, одним проходом по отсортированному массиву чисел, сделать ровно то, что написано в условии. Важно заметить, что размер входных данных был достаточно большим, поэтому читать эти данные нужно было достаточно быстро.
245G - Suggested Friends
В этой задаче планировалось решение за O(m2) с маленькой константой. Для начала предложим, что заданный во входных данных граф отношений связный, тогда количество вершин в нем n ≤ m + 1. Будем хранить такой граф в виде матрицы смежности a и ввиде списка смежных вершин для всех вершин. Теперь переберем вершину, которая будет общим другом предполагамых друзей. Далее переберем пару вершин, из списка смежных ей вершин, проверим, что они не соединены ребром, и сделаем инкремент в некоторую другую матрицу в ячейку b[u][v]. Эта матрица будет хранить количество, общих друзей между u, v, если u и v не соединены ребром.
После того, как мы построили матрицу b можно легко посчитать возможных друзей.
В случае, когда граф несвязный. Надо отдельно решить задачу для компонент связности. При этом надо аккуратно рассмотреть случай, когда возможный друг находится в другой компоненте.
245H - Queries for Number of Palindromes
Решение задачи динамическое программирование. dp[i][j] — количество палиндромов в подстроке s[i...j], isp[i][j] — является ли палиндромом подстрока s[i...j]. Переходы dp[i][j] = dp[i + 1][j] + dp[i][j - 1] - dp[i + 1][j - 1] + isp[i][j], isp[i][j] = 1, если isp[i + 1][j - 1] = 1, и s[i] = s[j]. Иначе isp[i][j] = 0.
Еще один способ решить С:
Заметим, что данным способом (х, 2х, 2х + 1) у нас хранится куча (2х и 2х + 1 — дети х). Листья в куче обрабатывать напрямую не получится (по условию), значит будем обрабатывать родителей листьев, пока листья не обнулятся. Забудем о нижнем уровне и повторим алгоритм. Получаем рекурсивное решение обходом кучи в глубину.
Я сдавал решение вообще без рекурсии. Достаточно пройтись просто от середины массива к началу. Уменьшать три элемента (корень и двух сыновей) на максимум из кол-во монет в листьях. Получаем решение за O(n).
хороший разбор!!!!!
По поводу задачи F. Я в дорешке написал декартово дерево с поддержкой размера поддеревьев, а потом резал по ключу p — m. Кто-нибудь знает как в сете получат количество элементов больше какого-нибудь ключа?
Насколько я знаю, сет не умеет находить такое быстрее чем за O(size(S)). Но в этой задаче вполне можно было обойтись массивом частичных сумм.
Я все-таки сдал стандартным сетом. Правда не считал количества элементов больших какого-то ключа. Я просто на каждой итерации удалял из сета ключи, которые уже точно в ответ не входили. А потом смотрел размер сета и либо выводил ответ либо переходил к следующей итерации. Код оказался довольно компактным.
С тем же успехом можно было использовать очередь и удалять за O(1) вместо O(logN) в сете. В задаче гарантируется отсортированность "записи заданы в хронологическом порядке", поэтому кандидат на удаление — первый из занесённых. Как раз то, что предлагает очередь.
а подскажите хорошую книжку по жадным алгоритмам и динамическому программированию? книгу Т. Кормен, Ч. Лейзер нашел, а есть что-то попроще, типа как в серии HeadFirst Labs?
По-моему, динамическому программированию нельзя обучиться, читая книжки. Надо просто решать и разбирать больше задач, и скилл решения динамик будет постепенно расти сам собой. Это я по себе так могу сказать. Вроде бы правда.
С одной стороны, да.
С другой стороны, динамику можно писать разными способами (вперед / назад, рекурсивно / не рекурсивно), усложнять (лексмин ответ, экономия памяти, внесения функции в параметры, быстрый пересчет частичными суммами), разнообразить объекты (на дереве, по подмножествам, по профилю) и т.д.
А далее вроде есть два пути развития:
Все это придумать самому :-)
Что-то научиться самому, а потом откуда-то черпать новые идеи.
Чтобы идеи появлялись, можно смотреть за более опытными, читать их код, спрашивать. А можно почитать книжку / послушать лекцию со стандартными примерами.
P.S. И, да, если человек писал динамику в своей жизни k <= 2 раз, то, чтобы справиться с (k+1)-й задачей на DP, ему обычно нужен или человек-помощник, или книжка/сайт с примерами.
Не плохая на мой взгляд статья Анатолия Присяжнюка ( aka AWPRIS)