Educational Round #4 — Разборы
Если , то ответ .
Если количество тостов не превышает вместимость сковороды , то на поджаривание всех тостов с двух сторон потребуется минуты.
Так как каждый тост нужно поджарить с обеих сторон, всего требуется прожарок. Каждая прожарка (одновременное расположение на сковороде до тостов) занимает минуты.
Следовательно, если , то минимальное время, необходимое для поджаривания всех тостов, равно минут.
Пример
Пусть имеется тоста, и сковорода вмещает одновременно тоста. Обозначим стороны тостов как .
Тогда все сторон можно поджарить за минут следующим образом:
Первая прожарка: и минуты;
Вторая прожарка: и минуты;
Третья прожарка: и минуты;
Итого: прожарки по минуты, то есть минут.
Читаем входные данные.
scanf("%d %d",&n,&k);Если , то ответ .
if (!n) res = 0; else
Если количество тостов не превышает вместимость сковороды, то на их поджаривание потребуется минуты.
if (n <= k) res = 4; else
{Каждый тост необходимо поджарить с двух сторон, то есть всего нужно прожарить сторон. При этом одна прожарка (независимо от количества тостов на сковороде, не превышающего её вместимость) занимает минуты.
res = 2 * n / k; if (2 * n % k) res++; res *= 2; }
Выводим ответ.
printf("%d\n",res);В классе ADA School расположены парт, образующих прямоугольную сетку из строк и столбцов. Каждая парта занята ровно одним учеником.
Перед началом занятия учитель решил немного перетасовать учеников. После перестановки должны быть выполнены два условия:
Каждый стол снова должен быть занят ровно одним учеником.
Каждый ученик должен пересесть за стол, примыкающий к его исходному месту, то есть расположенный непосредственно слева, справа, сверху или снизу.
Можно ли выполнить перестановку учеников, соблюдая оба условия?
Вход. Первая строка содержит количество тестов . Каждая из следующих строк содержит два целых числа и .
Выход. Для каждого теста выведите "YES", если перестановку учеников можно выполнить, и "NO" в противном случае.
2 5 5 4 4
NO YES
Рассмотрим случай, когда одна из размерностей сетки четная. Пусть, например, (количество строк) — четное. В этом случае перестановку учеников можно выполнить следующим образом:
Если и нечётные, то выполнить перестановку невозможно.
Рассмотрим сетку, раскрашенную в шахматном порядке: одни клетки — белые, другие — чёрные. При перемещении парта, стоявшая на чёрной клетке, должна перейти на белую, и наоборот. Чтобы перестановка всех парт была возможна, количество белых и чёрных клеток должно быть одинаковым. Следовательно, общее число парт должно быть чётным.
Однако при нечётных и общее количество парт является нечётным числом, что делает перестановку невозможной.
Читаем количество тестов .
scanf("%d", &t);
while (t--)
{Читаем входные данные каждого теста.
scanf("%d %d", &n, &m);Если и нечетные, то выводим ответ "NO".
if (n % 2 == 1 && m % 2 == 1)
puts("NO");Иначе выводим ответ "YES".
else
puts("YES");
}Агент Джонни Инглиш снова в деле!
На этот раз бесстрашному агенту и его помощнику Бофу поручено проследить за порядком на благотворительном мероприятии. Войдя в зал и осмотревшись, Инглиш понял, что для полной картины происходящего ему придётся немного прогуляться по залу, перекинуться парой слов с гостями и понаблюдать за официантами. Закончив разведку, Инглиш, уверенный в успехе, решил встретиться с Бофом и блеснуть перед ним своими невероятными аналитическими способностями. К сожалению, бедняга Боф на светских мероприятиях совершенно теряется, поэтому просто медленно следует за указаниями старшего агента.
Зал представляет собой квадрат на координатной плоскости со сторонами длиной , параллельными координатным осям. Вход находится в левом нижнем углу квадрата, в точке . Агент Инглиш собирается выбрать несколько гостей, расположенных в точках с целыми координатами, и поздороваться с каждым по очереди. Он не будет здороваться с одним и тем же гостем подряд, но иногда может ошибиться и вернуться к гостю, с которым уже здоровался. Тренированный агент движется со скоростью , а здоровается с гостями мгновенно. В это время Боф, двигаясь со скоростью , направляется напрямую к финальной точке маршрута, намеченного Инглишем.
Чтобы не вызывать подозрений, агент Инглиш хочет найти такой маршрут, при котором они с Бофом прибудут в точку встречи одновременно. К сожалению, у агента нет времени продумывать детали своего гениального плана, поэтому этим предстоит заняться Вам.
По заданным скоростям и найдите любой маршрут, начинающийся в точке и состоящий из точек с неотрицательными координатами, не превосходящими . При этом время прохождения маршрута со скоростью должно совпадать с временем прохождения того же маршрута со скоростью .
Вход. В одной строке заданы два натуральных числа и — скорости Бофа и агента Инглиша соответственно.
Выход. В первой строке выведите число — количество точек в маршруте. В следующих строках выведите пары целых чисел и — координаты точек в порядке обхода. Первой обязательно должна быть выведена точка . Точки могут повторяться, однако в маршруте не должно быть двух одинаковых точек подряд.
1 2
4 0 0 2 0 2 1 2 0
1 3
4 0 0 2 0 2 2 2 0
Джонни Инглиш и Боф достигают конечной точки одновременно за время . Джонни Инглиш преодолевает расстояние , а Боф . Отношение пройденных расстояний равно отношению скоростей: .
Построим такие пути конструктивно. Рассмотрим, например, маршрут из точек: . Здесь выбрана координата , чтобы ее значение было неотрицательным (по условию задачи ).
Длина пути Джонни Инглиша равна .
Длина пути Бофа равна .
Если скорости Джонни Инглиша и Бофа одинаковы, то точки и совпадают, а по условию задачи на маршруте не может быть двух одинаковых точек подряд. В этом случае в качестве решения можно выбрать, например, следующий маршрут: .
Читаем входные данные.
scanf("%d %d", &q, &p);Если скорости Джонни Инглиша и Бофа одинаковы, то они движутся из точки в точку .
if (q == p)
{
printf("2\n");
printf("%d %d\n", 0, 0);
printf("%d %d\n", 1, 0);
return 0;
}Если , то маршрут состоит из четырёх точек.
printf("4\n");
printf("%d %d\n", 0, 0);
printf("%d %d\n", 2 * q, 0);
printf("%d %d\n", 2 * q, p - q);
printf("%d %d\n", 2 * q, 0);Фермер Джон не умеет выполнять несколько задач одновременно. Он часто отвлекается, что затрудняет работу над долгосрочными проектами. Сейчас он пытается покрасить одну сторону своего коровника, но постоянно отвлекается на уход за коровами. В результате некоторые участки стены покрываются краской в несколько слоев, тогда как другие остаются менее окрашенными.
Сторону коровника можно представить в виде двумерной плоскости размером , где фермер Джон последовательно наносит краску в виде прямоугольных областей со сторонами, параллельными осям координат. Каждый прямоугольник задается координатами его нижнего левого и верхнего правого углов.
Фермер Джон хочет добиться равномерного покрытия стены, чтобы в ближайшее время не пришлось её перекрашивать. Однако он также не хочет тратить лишнюю краску. Оптимальным количеством слоев считается . Ваша задача — определить площадь участка стены, который покрыт ровно k слоями краски после нанесения всех прямоугольников.
Вход. Первая строка содержит два целых числа и — количество прямоугольников и требуемое число слоев покрытия.
Каждая из следующих строк содержит четыре целых числа , задающих прямоугольник с нижним левым углом в точке и верхним правым углом в точке .
Все координаты и находятся в диапазоне от до . Все заданные прямоугольники имеют положительную площадь.
Выход. Выведите одно число — площадь участка стены, покрытого ровно слоями краски.
3 2 1 1 5 5 4 4 7 6 3 3 8 7
8
Сначала рассмотрим одномерный вариант задачи. Пусть на числовой прямой задано отрезков , где . Необходимо определить, сколькими отрезками покрывается каждое целое число на этой прямой.
Объявим целочисленный массив , инициализируем его нулями. Затем для каждого отрезка выполним две операции:
Далее вычислим префиксные суммы в массиве . Значение равно количеству отрезков, содержащих число .
Например, пусть даны следующие отрезки: . Инициализируем массив :
Для каждого отрезка выполним две операции: .
Теперь рассмотрим двумерный вариант задачи. На плоскости задано прямоугольников с координатами . Требуется определить, сколькими прямоугольниками покрывается каждая клетка плоскости.
Объявим целочисленный двумерный массив , инициализируем его нулями. Затем для каждого прямоугольника выполним четыре операции:
После этого вычислим префиксные суммы в массиве . Значение будет равно числу прямоугольников, содержащих клетку .
Рассмотрим, например, прямоугольник .
Пример
Рассмотрим приведенный пример, содержащий три прямоугольника.
слоями краски покрыто квадратов.
Объявим двумерный массив .
#define MAX 1001 int dp[MAX][MAX];
Читаем входные данные.
scanf("%d %d", &n, &k);
while (n--)
{Для каждого прямоугольника выполним четыре операции.
scanf("%d %d %d %d", &a, &b, &c, &d);
dp[a][b]++;
dp[a][d]--;
dp[c][b]--;
dp[c][d]++;
}В переменной подсчитываем количество клеток стены, покрытых ровно слоями краски.
res = 0;
Вычисляем префиксные суммы в массиве .
for (i = 0; i < MAX; i++)
for (j = 0; j < MAX; j++)
{
if (i) dp[i][j] += dp[i - 1][j];
if (j) dp[i][j] += dp[i][j - 1];
if (i && j) dp[i][j] -= dp[i - 1][j - 1];Если клетка покрыта слоями краски, то увеличиваем на ,
if (dp[i][j] == k) res++; }
Выводим ответ.
printf("%d\n", res);Разделение строки называется палиндромным разбиением, если каждая подстрока в этом разбиении является палиндромом. Например, строка "ababbbabbababa" может быть разделена как "aba|b|bbabb|a|b|aba" — это пример палиндромного разбиения.
Определите наименьшее количество разрезов, необходимых для палиндромного разбиения заданной строки. Например:
для строки "" требуется минимум разреза, которые образуют разбиение "";
если строка уже является палиндромом, то потребуется разрезов;
если все символы строки длины n различны, то минимальное количество разрезов равно .
Вход. Одна строка длины не более .
Выход. Выведите минимальное количество разрезов, необходимых для палиндромного разбиения строки .
abbaazxzazbxbz
2
abccba
0
qwerty
5
Пусть — входная строка. Подстрока является палиндромом, если выполняется одно из следующих условий:
(подстрока пустая или состоит из одного символа);
и подстрока является палиндромом;
Пусть функция возвращает , если является палиндромом, и в противном случае. Значения сохраняем в ячейках .
Пусть функция возвращает минимальное количество разрезов, необходимых для палиндромного разбиения строки . Значения этой функции будем сохранять в ячейках массива . Тогда ответом на задачу будет значение .
Отметим, что , так как строка из одной буквы уже является палиндромом.
Для вычисления разрежем подстроку на две части: и . Рекурсивно решаем задачи на строках и . Ищем такое значение , для которого сумма будет минимальной. Слагаемое в сумме означает, что мы совершаем один разрез между символами и . Таким образом, получаем следующее рекуррентное соотношение:
Пример
Например, для строки "" из символов имеем:
Сумма принимает наименьшее значения при :
Так как строки "" и "" являются палиндромами, то .
Объявим константы, входную строку и рабочие массивы.
#define MAX 1001 #define INF 0x3F3F3F3F int dp[MAX][MAX], pal[MAX][MAX]; string s;
Функция проверяет, является ли подстрока палиндромом. Подстрока является палиндромом, если и является палиндромом. Значения сохраняем в ячейках массива .
int IsPal(int i, int j)
{
if (i >= j) return pal[i][j] = 1;
if (pal[i][j] != -1) return pal[i][j];
return pal[i][j] = (s[i] == s[j]) && IsPal(i + 1, j - 1);
}Функция возвращает минимальное количество разрезов, необходимых для палиндромного разбиения подстроки .
int f(int i, int j)
{Если , подстрока состоит из одного символа, и резать её нет смысла. Если , подстрока считается пустой, и разрезы также не требуются.
if (i >= j) return 0;
Если значение уже было вычислено ранее, возвращаем сохранённое значение из массива .
if (dp[i][j] != INF) return dp[i][j];
Если подстрока является палиндромом, то резать ее смысла нет. В этом случае минимальное количество разрезов равно .
if (IsPal(i, j)) return dp[i][j] = 0;
Разрежем подстроку на две части: и . Далее рекурсивно решаем задачи для обеих частей: и . Ищем такое значение , для которого сумма наименьшая.
for (int k = i; k < j; k++)
dp[i][j] = min(dp[i][j], f(i, k) + f(k + 1, j) + 1);Возвращаем ответ .
return dp[i][j]; }
Основная часть программы. Читаем входную строку.
cin >> s;
Инициализируем массивы.
memset(dp, 0x3F, sizeof(dp)); memset(pal, -1, sizeof(pal));
Вычисляем и выводим ответ.
res = f(0, s.size() - 1); cout << res << endl;
Заданы зданий с высотами . Сделайте так, чтобы все здания имели одинаковую высоту. Для этого разрешается как удалять кирпичи из зданий, так и добавлять их. Каждое добавление или удаление одного кирпича требует определённой платы, которая задаётся для каждого здания отдельно.
Найдите минимальную общую стоимость реконструкции, при которой все здания будут иметь одинаковую высоту (где — любое целое неотрицательное число), то есть выполняется условие .
Для простоты будем считать, что каждое здание представляет собой вертикальную колонну из одинаковых кирпичей.
Вход. Первая строка содержит одно целое число — количество зданий.
Вторая строка содержит целых чисел — высоты зданий .
Третья строка содержит целых чисел — стоимость добавления или удаления одного кирпича для соответствующего здания.
Выход. Выведите одно целое число — минимальную суммарную стоимость, за которую все здания можно сделать одинаковой высоты.
4 2 3 1 4 10 20 30 40
110
3 1 2 3 10 100 1000
120
Отсортируем здания по их высотам так, чтобы выполнялось неравенство: . Если несколько зданий имеют одинаковую высоту, упорядочим их дополнительно по возрастанию стоимости добавления или удаления кирпича.
Рассмотрим стоимость, необходимую для того, чтобы привести все здания к высоте . Она равна
Функция , описывающая эту стоимость, является вогнутой и имеет глобальный минимум, который можно найти с помощью тернарного поиска.
Пример
Рассмотрим первый пример. Отсортируем здания по их высотам:
Найдём значения функции для всех возможных высот зданий:
,
,
,
Минимальное значение функции достигается при ; в этом случае .
Каждое здание имеет высоту и стоимость добавления или удаления одного этажа. Информация о зданиях хранится в массиве .
#define MAX 100001
struct Building
{
int h, cost;
};
Building b[MAX];Функция cmp является компаратором для сортировки зданий. Сначала здания сортируются по возрастанию высоты, а при равных высотах — по возрастанию стоимости.
int cmp(Building a, Building b)
{
if (a.h == b.h)
return a.cost < b.cost;
return a.h < b.h;
}Функция f вычисляет стоимость, необходимую для того, чтобы сделать все постройки одинаковой высоты с высотой здания номер .
long long f(int x)
{
long long ret = 0;
for (int i = 1; i <= n; i++)
ret += 1LL * abs(b[x].h - b[i].h) * b[i].cost;
return ret;
}Функция ternary_search при помощи тернарного поиска ищет такое значение , при котором минимально. Изначально границы диапазона заданы как .
long long ternarySearch(int left, int right)
{
while (right - left >= 3)
{
int a = left + (right - left) / 3;
int b = left + 2 * (right - left) / 3;
if (f(a) > f(b))
left = a;
else
right = b;
}Разность между и не превышает . В этом случае минимальное значение на отрезке находим полным перебором.
long long res = f(left++);
while (left <= right)
res = min(res, f(left++));
return res;
}Основная часть программы. Читаем входные данные.
scanf("%d", &n);
for (i = 1; i <= n; i++)
scanf("%d", &b[i].h);
for (i = 1; i <= n; i++)
scanf("%d", &b[i].cost);Сортируем здания.
sort(b + 1, b + n + 1, cmp);
Вычисляем и выводим ответ. Здания нумеруются от до .
printf("%lld\n", ternarySearch(1, n));С домино можно придумать множество развлечений. Например, дети любят выстраивать костяшки домино в линию, располагая их рядом друг с другом. Если толкнуть одну костяшку, она упадет и собьет соседнюю, та, в свою очередь, собьет следующую, и так далее, пока цепная реакция не прекратится. Однако могут существовать такие расстановки, при которых не все костяшки упадут. В этом случае необходимо дополнительно толкнуть еще одну костяшку, чтобы цепная реакция продолжилась.
По заданной расстановке домино определите минимальное количество костяшек, которые необходимо толкнуть рукой, чтобы все домино упали.
Вход. Первая строка содержит два целых числа и , где — количество костяшек домино, а — количество строк, описывающих их взаимодействие. Костяшки пронумерованы числами от до .
Каждая из следующих строк содержит два числа и , которые означают, что если костяшка с номером упадет, то она собьет костяшку с номером .
Выход. Выведите одно число — минимальное количество костяшек, которые нужно толкнуть, чтобы все домино упали.

3 2 1 2 2 3
1
5 5 2 3 1 2 4 2 5 3 5 4
2
Рассмотрим задачу нахождения сильных компонент связности в ориентированном графе. Каждую вершину графа из одной сильно связной компоненты окрасим в один и тот же цвет. Пусть обозначает цвет -й вершины. Число различных цветов окраски будет равно числу компонент сильной связности.
Очевидно, что если толкнуть одну костяшку домино, то обязательно упадут все костяшки, принадлежащие той же сильной компоненте связности. Обозначим через количество сильных компонент связности.
Объявим массив длины , где , если необходимо толкнуть домино из -й компоненты связности. Теперь определим, для каких значений следует установить равным .
Переберем все ребра графа. Нас интересуют только те ребра, которые соединяют разные компоненты связности. Если такое ребро имеет вид (где ), то установим . Это означает, что нет необходимости толкать домино из компоненты цвета , так как, толкнув домино из компоненты цвета , мы гарантированно столкнем все домино из компоненты цвета .
После обработки всех ребер остается подсчитать количество единиц в массиве . Это и будет минимальное количество костяшек, которые следует толкнуть.
Пример
Графы, приведенные в примерах, имеют следующий вид:
В первом примере достаточно столкнуть домино номер .
Во втором примере необходимо столкнуть домино с номерами и .
Граф из первого примера имеет сильно связные компоненты.
поскольку существует ребро , нет необходимости толкать домино номер ;
поскольку существует ребро , нет необходимости толкать домино номер ;
Входной граф храним в списке смежности .
Обратный граф (граф, в котором изменены все направления ребер) храним списке смежности .
vector<vector<int> > g, gr; vector<int> used, top, color;
Функция dfs1 выполняет поиск в глубину на входном графе. В массив заносим вершины в порядке завершения их обработки в процессе поиска в глубину.
void dfs1(int v)
{
used[v] = 1;
for (int to : g[v])
if (!used[to]) dfs1(to);
top.push_back(v);
}Функция dfs2 выполняет поиск в глубину на обратном графе. Все вершины, которые посещаются в ходе рекурсивного вызова функции , принадлежат одной компоненте сильной связности. Каждую из таких вершин окрашиваем в цвет .
void dfs2(int v, int c)
{
color[v] = c;
for (int to : gr[v])
if (color[to] == -1) dfs2(to, c);
}Основная часть программы. Читаем входные данные. Строим обратный граф.
scanf("%d %d",&n,&m);
g.resize(n+1);
gr.resize(n+1);
cnt = 0;
for(i = 0; i < m; i++)
{
scanf("%d %d",&a,&b);
g[a].push_back(b);
gr[b].push_back(a);
}Запускаем поиск в глубину на входном графе. Порядок завершения обработки вершин сохраняем в массиве .
used.resize(n+1); for(i = 1; i <= n; i++) if (!used[i]) dfs1(i);
Затем выполняем поиск в глубину на обратном графе. Вершины обратного графа обрабатываются в порядке, в котором они находятся в массиве , начиная с конца (справа налево). Вершины, принадлежащие одной компоненте сильной связности, окрашиваются в один цвет. Текущий цвет раскраски содержится в переменной .
Для удобства дальнейшей обработки обернем массив , после чего будем обрабатывать вершины в нем слева направо.
color.assign(n+1,-1); reverse(top.begin(), top.end()); c = 0; for (int v : top) if (color[v] == -1) dfs2(v, c++);
Переменная содержит количество компонент сильной связности.
used.assign(c,1); for (i = 1; i < g.size(); i++) for (int to : g[i])
Перебираем все ребра графа . Проверяем, находятся ли вершины и в разных сильно связных компонентах. Это так, если они окрашены в разные цвета. В таком случае, если столкнуть любое домино из компоненты сильной связности, которой принадлежит вершина , то обязательно упадет и домино . Это означает, что нет смысла сталкивать домино цвета . Поэтому устанавливаем .
if (color[i] != color[to]) used[color[to]] = 0;
В переменной подсчитываем количество домино, которые следует столкнуть. Если для какого-либо цвета выполняется условие , это означает, что никакое домино цвета не упадет, независимо от того, какое домино другого цвета мы столкнем. В этом случае обязательно придется столкнуть хотя бы одно домино цвета .
c = 0; for(i = 0; i < used.size(); i++) if (used[i]) c++;
Выводим ответ.
printf("%d\n",c);Задан ориентированный граф с вершинами и ребрами. Вершины пронумерованы от до . Для каждого задано направленное ребро из вершины в вершину .
Граф не содержит направленных циклов.
Найдите длину самого длинного направленного пути в . Под длиной пути понимается количество рёбер в этом пути.
Вход. Первая строка содержит два целых числа: количество вершин и количество ребер графа. Каждая из следующих строк содержит два целых числа , описывающих ориентированное ребро из вершины в вершину .
Выход. Выведите длину самого длинного направленного пути в графе .

4 5 1 2 1 3 3 2 2 4 3 4
3
6 3 2 3 4 5 5 6
2
5 8 5 3 2 3 2 4 5 2 5 1 1 4 4 3 1 3
3
Найдем топологическую сортировку графа. Обозначим через максимальное количество городов, которое можно посетить по пути, начиная из вершины .
Рассмотрим вершины в порядке топологической сортировки. Пусть — текущая вершина. Обозначим через вершины, в которые ведут ребра из . Тогда обновим значение:
Изначально установим .
После обработки всех вершин находим наибольшее значение в массиве . Оно равно длине максимального пути в графе.
Пример
Слева приведен граф из первого примера. Справа — из второго. Самые длинные пути выделены.
В первом примере самый длинный путь равен .
Во втором примере самый длинный путь равен .
Объявим список смежности графа .
vector<vector<int> > g;
Функция dfs выполняет поиск в глубину, начиная с вершины . Сортируем вершины графа топологически и записываем их в массив .
void dfs(int v)
{
used[v] = 1;
for (int to : g[v])
if (used[to] == 0) dfs(to);
top.push_back(v);
}Основная часть программы. Читаем входные данные. Строим список смежности ориентированного графа.
scanf("%d %d", &n, &m);
g.resize(n + 1);
while (m--)
{
scanf("%d %d", &a, &b);
g[a].push_back(b);
}Запускаем поиск в глубину на несвязном графе.
used.resize(n + 1); for (i = 1; i <= n; i++) if (used[i] == 0) dfs(i);
Перебираем вершины графа в порядке, обратном их топологической сортировке, то есть перебираем вершины массива слева направо.
dp.resize(n + 1);
for (int u : top)
{Путь из вершины в саму себя имеет длину (инициализация).
dp[u] = 0;
Перебираем вершины , смежные с . Рассматриваем ребро .
for (int v : g[u])
Для всех таких вершин ищем максимум среди значений .
dp[u] = max(dp[u], dp[v] + 1); }
Находим и выводим наибольший элемент в массиве . Он соответствует длине самого длинного пути в графе.
res = *max_element(dp.begin(), dp.end());
printf("%d\n", res);Вам дана строка длины и запросов. Каждый запрос имеет формат , означающий что необходимо отсортировать подстроку, состоящую из символов на позициях до включительно, в неубывающем порядке, если , или в невозрастающем порядке, если .
После выполнения всех запросов выведите полученную итоговую строку.
Вход. В первой строке заданы два целых числа и — длина строки и количество запросов соответственно.
Во второй строке содержится строка , состоящая только из строчных латинских букв.
Каждая из следующих строк содержит три целых числа и или , описывающих запрос.
Выход. Выведите строку после выполнения всех запросов.
10 5 abacdabcda 7 10 0 5 8 1 1 4 0 3 6 0 7 10 1
cbcaaaabdd
Создадим деревьев отрезков для подсчёта сумм — по одному для каждой буквы от '' до ''. Каждое дерево должно поддерживать множественные модификации.
Например, для буквы '' и строки "" соответствующее дерево отрезков будет выглядеть так:
С помощью такого дерева можно за логарифмическое время определить, сколько раз заданная буква встречается на отрезке .
Выполнение запроса на отрезке сводится к сортировке подсчётом. Сначала в массиве подсчитывается, сколько раз каждая буква встречается на данном отрезке.
Далее, в зависимости от значения , мы "пересобираем" этот участок строки:
если , то символы располагаются в неубывающем порядке — то есть буквы от '' к '';
если , то в невозрастающем порядке, от '' к ''.
Для этого на всём отрезке для каждой буквы '' + , количество которой известно из массива cnt, мы выставляем в дереве отрезков соответствующее количество единиц, начиная с позиции (если сортируем по возрастанию) или с позиции (если по убыванию).
Например, пусть на позициях находится подстрока "". Подсчитаем количество каждой буквы на этом интервале:
Буква '' встречается раза;
Буква '' встречается раза;
Буква '' встречается раза;
Буква '' встречается раз;
Чтобы отсортировать буквы на отрезке по возрастанию, необходимо для дерева отрезков каждой буквы сначала обнулить значения на интервале , а затем выполнить следующие операции:
Для буквы '' на отрезке каждому элементу присвоить ;
Для буквы '' на отрезке каждому элементу присвоить ;
Для буквы '' на отрезке каждому элементу присвоить ;
Для буквы '' на отрезке каждому элементу присвоить ;
Такое "обновление" деревьев отрезков фактически моделирует перестановку символов на нужном участке без непосредственного изменения самой строки.
После обработки всех запросов итоговая строка восстанавливается из содержимого деревьев отрезков: для каждой буквы последовательно выполняется обход её дерева, и в тех позициях, где значение равно , в результирующую строку записывается соответствующий символ.
Для каждой буквы от '' до '' создадим собственное дерево отрезков. Таким образом, всего используется деревьев, каждое из которых поддерживает операции суммирования и множественную модификацию.
#define MAX 100010
#define ALPHABET 26
struct node
{
int sum, add;
} SegTree[4*MAX][ALPHABET];Объявим массив для подсчета количества вхождений каждой буквы.
int cnt[ALPHABET];
Построим деревьев отрезков по строке — по одному для каждой буквы латинского алфавита.
void build(int v, int lpos, int rpos)
{
if (lpos == rpos)
{
SegTree[v][s[lpos] - 'a'].sum = 1;
for (int i = 0; i < ALPHABET; i++)
SegTree[v][i].add = -1;
}
else
{
int mid = (lpos + rpos) / 2;
build(v * 2, lpos, mid);
build(v * 2 + 1, mid + 1, rpos);
for (int i = 0; i < ALPHABET; i++)
{
SegTree[v][i].sum =
SegTree[v * 2][i].sum + SegTree[v * 2 + 1][i].sum;
SegTree[v][i].add = -1;
}
}
}Вершине соответствует отрезок , при этом . Выполним проталкивание этой вершины в дереве отрезков с номером .
void Push(int v, int lpos, int mid, int rpos, int ver)
{
if (SegTree[v][ver].add != -1)
{
SegTree[2 * v][ver].add = SegTree[v][ver].add;
SegTree[2 * v][ver].sum = (mid - lpos + 1) * SegTree[v][ver].add;
SegTree[2 * v + 1][ver].add = SegTree[v][ver].add;
SegTree[2 * v + 1][ver].sum = (rpos - mid) * SegTree[v][ver].add;
SegTree[v][ver].add = -1;
}
}Вершине соответствует отрезок . В дереве отрезков с номером установим значение для всех элементов с индексами от до .
void SetValue(int v, int lpos, int rpos, int left, int right,
int val, int ver)
{
if (left > right) return;
if ((lpos == left) && (rpos == right))
{
SegTree[v][ver].add = val;
SegTree[v][ver].sum = (right - left + 1) * val;
return;
}
int mid = (lpos + rpos) / 2;
Push(v, lpos, mid, rpos, ver);
SetValue(2 * v, lpos, mid, left, min(mid, right), val, ver);
SetValue(2 * v + 1, mid + 1, rpos, max(left, mid + 1), right,
val, ver);
SegTree[v][ver].sum =
SegTree[2 * v][ver].sum + SegTree[2 * v + 1][ver].sum;
}Вершине соответствует отрезок . Найдем сумму элементов на отрезке в дереве отрезков с номером .
int Summa(int v, int lpos, int rpos, int left, int right, int ver)
{
if (left > right) return 0;
if ((lpos == left) && (rpos == right)) return SegTree[v][ver].sum;
int mid = (lpos + rpos) / 2;
Push(v, lpos, mid, rpos, ver);
return Summa(2 * v, lpos, mid, left, min(mid, right), ver) +
Summa(2 * v + 1, mid + 1, rpos, max(left, mid + 1), right, ver);
}Выполним проталкивание во всех вершинах дерева отрезков с номером . Затем внесём букву + '' во все позиции строки , где ее значение в дереве отрезков равно .
void get(int v, int lpos, int rpos, int ver)
{
if (SegTree[v][ver].sum == 0) return;
if (lpos == rpos)
{
s[lpos] = ver + 'a';
return;
}
int mid = (lpos + rpos) / 2;
Push(v, lpos, mid, rpos, ver);
get(2 * v, lpos, mid, ver);
get(2 * v + 1, mid + 1, rpos, ver);
}Основная часть программы. Читаем входные данные. По входной строке строим деревьев отрезков — по одному для каждой буквы латинского алфавита.
cin >> n >> q; cin >> s; s = " " + s; build(1,1,n);
Последовательно обрабатываем запросов.
for (i = 0; i < q; i++)
{
cin >> l >> r >> command;Выполним сортировку подсчетом всех букв на отрезке . Для этого сначала подсчитаем количество вхождений каждой буквы '' + на данном интервале и запишем результат в .
for (j = 0; j < ALPHABET; j++)
cnt[j] = Summa(1, 1, n, l, r, j);Затем, начиная с позиции , будем двигаться вправо или влево в зависимости от значения переменной .
pos = (command == 1) ? l : r;
for (j = 0; j < ALPHABET; j++)
{
if (!cnt[j]) continue;
SetValue(1, 1, n, l, r, 0, j);
if (command)
{
SetValue(1, 1, n, pos, pos + cnt[j] - 1, 1, j);
pos += cnt[j];
}
else
{
SetValue(1, 1, n, pos - cnt[j] + 1, pos, 1, j);
pos -= cnt[j];
}
}
}Генерация результирующей строки выполняется на основе содержимого деревьев отрезков. Функция , используя данные -го дерева отрезков, расставляет все буквы '' + на соответствующие позиции строки . Функция get выполняет полное проталкивание данных, проходя по всем вершинам дерева отрезков за время .
for (i = 0; i < ALPHABET; i++) get(1, 1, n, i);
Выводим результирующую строку.
cout << s.substr(1);
Схема метро состоит из станций, расположенных на линиях. Каждая станция принадлежит одной или нескольким линиям (в этом случае на станции можно выполнить пересадку на любую из проходящих через неё линий). Каждая линия включает не менее двух станций и пересекается хотя бы с одной другой линией. Схема метро является связной.
Передвижение между двумя соседними станциями одной линии возможно в обоих направлениях и занимает минуты. Пересадка с одной линии на другую в пределах одной станции занимает минуту. Другими затратами времени можно пренебречь.
Определите минимальное время, необходимое менеджеру фирмы «Диез-продукт», чтобы добраться от станции до здания офиса компании, расположенного рядом со станцией .
Вход. В первой строке заданы два натуральных числа и . В следующих строках указаны последовательные номера станций каждой из линий метро. В последней строке заданы номера и линии начальной и конечной станций. Все числа натуральные и не превышают .
Выход. Выведите минимальное время движения между указанными станциями.
Пример. В приведенном примере необходимо найти кратчайший маршрут от станции на линии до станции на линии .
Идём по линии от станции к станции : минуты.
Совершаем пересадку на станции с линии на линию : минута.
Идём по линии от станции к станции : минуты.
Совершаем пересадку на станции с линии на линию : минута.
Идём по линии от станции к станции : минуты.
Итого весь путь займет минут.

7 3 2 1 3 6 1 4 5 5 7 2 1 7 3
10
Кратчайшее расстояние от источника до вершины , принадлежащей линии , храним в , где . Граф представляется в виде списков смежности. Для каждого ребра, помимо направления и стоимости, дополнительно хранится номер линии метро, к которой принадлежит вершина, в которую это ребро ведёт.
Алгоритм начинается со станции , расположенной на линии . Искомое значение — минимальная стоимость пути, за которую можно добраться до станции , находящейся на линии .
При переходе между вершинами необходимо проверять, принадлежат ли они одной линии: если линии различаются, к стоимости добавляется (пересадка между линиями).
С помощью алгоритма Дейкстры находим кратчайшее расстояние из состояния в состояние .
Пример
В приведённом примере требуется найти кратчайший маршрут от станции на линии до станции на линии .
Идём по линии от станции к станции : минуты.
Совершаем пересадку на станции с линии на линию : минута.
Идём по линии от станции к станции : минуты.
Совершаем пересадку на станции с линии на линию : минута.
Идём по линии от станции к станции : минуты.
Итого весь путь займет минут.
Количество вершин в графе не более . Количество линий не более .
#define MAX 71 #define LINES 11
Кратчайшее расстояние от источника до вершины , принадлежащей линии , храним в . Объявим массив для хранения кратчайших расстояний.
int dist[MAX][LINES];
Структура node содержит информацию о ребре:
— направление ребра;
— стоимость ребра;
— какой линии метро ребро принадлежит;
struct node
{
int to, cost, line;
node(int to, int cost, int line) : to(to), cost(cost), line(line) {}
};Структура node также используется в очереди с приоритетами, где
— текущая вершина;
— минимальная стоимость проезда до вершины (на линии );
— номер линии метро, на которой находится вершина ;
В очереди с приоритетами вершины упорядочиваются по возрастанию стоимости проезда.
bool operator< (node a, node b)
{
return a.cost > b.cost;
}Объявим список смежности для представления графа.
vector<vector<node> > g;
Основная часть программы. Читаем входные данные.
scanf("%d %d", &n, &l);
g.resize(n + 1);Читаем -ую линию метро. Время движения между соседними станциями и равно .
for (i = 1; i <= l; i++)
{Читаем станцию — первую станцию -ой линии метро. За станцией следует станция .
scanf("%d ", &u);
while (scanf("%d%c", &v, &ch))
{Движение между станциями возможно в обоих направлениях, при этом стоимость ребра равна .
g[u].push_back(node(v, 2, i));
g[v].push_back(node(u, 2, i));
u = v;Как только достигаем конца строки, завершаем чтение данных текущей линии метро.
if (ch == '\n') break; } }
Инициализируем массив для хранения кратчайших расстояний.
for (i = 1; i <= n; i++) for (j = 1; j <= l; j++) dist[i][j] = 2000000000;
Читаем информацию о начальной и конечной станции. Для каждой станции задается линия, которой она принадлежит.
scanf("%d %d %d %d", &s, &line1, &f, &line2);Стартуем со станции , находящейся на линии .
dist[s][line1] = 0;
Заносим в очередь структуру node, содержащую начальную вершину на линии . Стоимость достижения начальной вершины равна .
priority_queue<node, vector<node> > pq;
pq.push(node(s, 0, line1)); // (v, cost, line)
while (!pq.empty())
{Извлекаем из очереди вершину с наименьшей стоимостью.
int cost = pq.top().cost; int v = pq.top().to; int l1 = pq.top().line; pq.pop(); // dist[v][l1] = cost
Перебираем все вершины, смежные с .
for (i = 0; i < g[v].size(); i++)
{Рассматриваем ребро со стоимостью , принадлежащее линии .
int to = g[v][i].to;
int w = g[v][i].cost;
int l2 = g[v][i].line;Если двигаться по ребру , то стоимость пути составит .
int tot_cost = cost + w;
Если вершины и принадлежат разным линиям, необходимо совершить пересадку с линии на линию .
if (l1 != l2) tot_cost += 1;
Если движение по ребру улучшает текущее значение, обновляем и помещаем вершину в очередь (стоимость ее достижения составляет и она находится на линии ).
if (tot_cost < dist[to][l2])
{
dist[to][l2] = tot_cost;
pq.push(node(to, tot_cost, l2));
}
}
}Выводим ответ — минимальную стоимость пути до вершины на линии .
printf("%d\n", dist[f][line2]);