Basecamp
    Главная
    Задачи
    Соревнования
    Курсы
    Рейтинг
    Посты
    Store
    Discord
Educational Round #1 — Разборы
Войти
medv
•Разборы•20 дней назад

Educational Round #1 — Разборы

Eolymp #1265. Египет

Ещё в древности египтяне знали, что треугольник со сторонами 3, 4 и 5 является прямоугольным, а его прямой угол — наибольшим. Определите, обладают ли тем же свойством и другие треугольники.

Вход. Состоит из нескольких тестов, оканчивающихся строкой 0 0 0. Каждый тест содержит три натуральных числа — длины сторон очередного треугольника. Все числа не превышают 30000.

Выход. Для каждого теста выведите в отдельной строке "right", если треугольник является прямоугольным, или "wrong" в противном случае.

Пример входа
6 8 10
25 52 60
5 12 13
0 0 0
Пример выхода
right
wrong
right
Открыть задачу
Решение

Пусть a,b,c — длины сторон треугольника. Треугольник является прямоугольным, если выполняется одно из следующих равенств:

  • a2=b2+c2 (гипотенузой является сторона a)

  • b2=a2+c2 (гипотенузой является сторона b)

  • с2=a2+b2 (гипотенузой является сторона с)

Реализация алгоритма

Читаем входные данные до конца файла.

while(scanf("%d %d %d",&a,&b,&c))
{

Если встретились три нуля, то завершаем работу программы.

  if (a + b + c == 0) break;

Проверяем, является ли треугольник прямоугольным. В зависимости от результата выводим ответ.

  if ((a * a + b * b == c * c) ||
      (a * a + c * c == b * b) ||
      (b * b + c * c == a * a))
    puts("right");
  else
    puts("wrong");
}
C++
7 lines
154 bytes
Eolymp #4857. Олимпиада в Хогвартсе

В Хогвартсе проходит традиционная ежегодная олимпиада по теории магии среди младшекурсников. Завхозу школы, Аргусу Филчу, поручено распределить студентов по аудиториям.

Каждый факультет выставил на олимпиаду своих лучших учеников: от Гриффиндора участвуют g студентов, от Слизерина — s студентов, Пуффендуй представляет h студентов, а Когтевран — r студентов. В распоряжении Филча имеется m аудиторий.

На аудитории наложено особое заклятие расширения, благодаря которому каждая аудитория может вместить любое количество студентов. Однако необходимо учитывать, что ученики одного факультета, оказавшиеся в одной аудитории, могут воспользоваться возможностью для жульничества, обмениваясь идеями по решению задач. Поэтому количество студентов одного факультета в одной аудитории должно быть минимально возможным.

Назовём рассадку оптимальной, если она минимизирует максимальное количество студентов одного факультета в любой аудитории.

Определите, какое минимальное количество студентов одного факультета Филчу всё же придётся посадить в одну аудиторию даже при оптимальной рассадке.

Вход. Первая строка содержит четыре целых числа g,s,h и r (1≤g,s,h,r≤1000) — количество учеников, представляющих факультеты Гриффиндор, Слизерин, Пуффендуй и Когтевран соответственно.

Во второй строке задано одно целое число m (1≤m≤1000) — количество доступных аудиторий.

Выход. Выведите одно целое число — минимальное возможное количество студентов одного факультета в одной аудитории при оптимальной рассадке.

Пример входа 1
4 3 4 4
2
Пример выхода 1
2
Пример входа 2
15 14 13 14
5
Пример выхода 2
3
Открыть задачу
Решение

На самом крупном факультете учится mx=max(g,s,h,r) студентов. Если попытаться равномерно распределить их по m аудиториям, то в какой-то из них окажется не менее ⌈mx/m⌉ студентов. Именно такое минимальное количество студентов одного факультета придётся посадить в одну аудиторию даже при оптимальной рассадке.

Реализация алгоритма

Читаем входные данные.

scanf("%d %d %d %d", &g, &s, &h, &r);
scanf("%d", &m);

Вычисляем mx=max(g,s,h,r).

mx = g;
if (s > mx) mx = s;
if (h > mx) mx = h;
if (r > mx) mx = r;

Определим максимальное количество студентов, которое может оказаться в одной аудитории: res=⌈mx/m⌉.

res = mx / m;
if (mx % m > 0) res++;

Выводим ответ.

printf("%d\n", res);
Eolymp #8642. Четырехзначные числа Армстронга

Четырехзначное число называется числом Армстронга, если сумма четвертых степеней его цифр равна самому числу.

Например: 8208=84+24+04+84, следовательно 8208 является числом Армстронга.

Выведите все числа Армстронга от a до b.

Вход. Два целых числа a и b (1000≤a≤b≤9999).

Выход. Выведите в одной строке все числа Армстронга в диапазоне от a до b.

Пример входа
1000 3000
Пример выхода
1634
Открыть задачу
Решение

Переберем числа от a до b. Для каждого числа i=xyzu​ (a≤i≤b) выделим цифры тысяч x, сотен y, десятков z и единиц u. Если число i является числом Армстронга (i=x4+y4+z4+u4), то выводим его.

Реализация алгоритма

Читаем входные данные.

scanf("%d %d", &a, &b);

Перебираем числа от a до b.

for (i = a; i <= b; i++)
{

Выделяем цифры числа i=xyzu​.

  x = a / 1000;
  y = i / 100 % 10;
  z = i / 10 % 10;
  u = i % 10;

Если число i является числом Армстронга (i=x4+y4+z4+u4), то выводим его.

  if (x * x * x * x + y * y * y * y + z * z * z * z + 
      u * u * u * u == i) printf("%d ", i);
}

printf("\n");
Eolymp #11339. Разделите на 2 группы

Разделите числа 1,2,...,n на две группы так, чтобы абсолютная разность между суммами элементов этих групп была наименьшей из возможных.

Вход. Одно целое число n (2≤n≤105).

Выход. В первой строке выведите два целых числа — количество элементов в первой и второй группах.

Во второй строке выведите элементы первой группы, а в третьей — элементы второй группы.

Элементы внутри каждой группы могут быть выведены в любом порядке. Каждое число от 1 до n должно быть включено ровно в одну из групп.

Пример входа 1
4
Пример выхода 1
2 2
1 4
2 3
Пример входа 2
5
Пример выхода 2
3 2
1 2 4
3 5
Открыть задачу
Решение

Заметим, что если числа n и n−3 поместить в одну группу, а n−1 и n−2 поместить в другую (сумма чисел в каждой группе будет одинаковой), то задача сведётся к аналогичной задаче меньшей размерности, а именно к разбиению на группы чисел 1,2,...,n−4.

Для n≤3 числа распределим по группам следующим образом:

  • Если остаются три числа 1,2,3, то их можно разделить на две группы: {1,2} и {3}.

  • Если остаются два числа 1,2, то разделим их на группы: {1} и {2}. Разность сумм чисел в группах составит 1.

  • Если остается одно число 1, то его можно поместить в любую группу. Разность сумм чисел в группах составит 1.

Если сумма всех чисел от 1 до n четная, то их можно разделить на две группы с одинаковой суммой.

Если сумма всех чисел от 1 до n нечетная, то их можно разделить на две группы с разницей сумм, равной 1.

Пример

Пусть n=10. Числа в группы размещаем следующим образом:

Пусть n=11. Числа в группы размещаем следующим образом:

Реализация алгоритма

Читаем входное значение n.

scanf("%d", &n);

Устанавливаем начальные суммы чисел в группах s1​ и s2​ равными 0.

s1 = s2 = 0;

Перебираем числа от n до 1 по убыванию.

for (i = n; i > 0; i--)

Если s1​<s2​, то заносим число i в первую группу. Иначе заносим число i во вторую группу.

  if (s1 < s2)
  {
    s1 += i;
    a.push_back(i);
  }
  else
  {
    s2 += i;
    b.push_back(i);
  }
C++
10 lines
114 bytes

Выводим размеры групп.

printf("%d %d\n", a.size(), b.size());

Выводим числа первой группы.

for (i = 0; i < a.size(); i++)
  printf("%d ", a[i]);
printf("\n");

Выводим числа второй группы.

for (i = 0; i < b.size(); i++)
  printf("%d ", b[i]);
printf("\n");
Eolymp #1212. Бесконечная последовательность - 2

Определим бесконечную последовательность А следующим образом:

Ai​={1,i≤0A⌊i/p⌋−x​+A⌊i/q⌋−y​,i≥1​

По заданным значениям n,p,q,x,y вычислите An​.

Вход. Пять целых чисел n,p,q,x,y (0≤n≤1013,2≤p,q≤109,0≤x,y≤109).

Выход. Выведите значение An​.

Пример входа 1
12 2 3 1 0
Пример выхода 1
8
Пример входа 2
10000000 2 3 10000000 10000000
Пример выхода 2
2
Открыть задачу
Решение

Поскольку n≤1013, хранение всех значений Ai​ (i=0,1,...,n) невозможно ни с использованием массива, ни с помощью структуры map из-за ограничений по памяти. Поэтому для вычислений будем использовать рекурсию, заданную в рекуррентном соотношении.

При этом значения Ai​ для которых i<5000000, будем сохранять в массиве m, чтобы избежать повторных вычислений и ускорить выполнение программы.

Реализация алгоритма

Для хранения значений Ai​ (i<5000000) объявим массив m.

#define MAX 5000000
long long m[MAX];

Функция f вычисляет значение An​.

long long f(long long n, long long p, long long q, 
            long long x, long long y)
{

Если n≤0, то An​=1.

  if (n <= 0) return 1;

Если n<5000000 и значение m[n] уже вычислено (отлично от нуля), то возвращаем его.

  if ((n < MAX) && m[n]) return m[n];

Выполняем рекурсивное вычисление значения An​ в соответствии с формулой, приведённой в условии задачи.

  long long temp = f(n/p-x,p,q,x,y) + f(n/q-y,p,q,x,y);

Если n<5000000, сохраняем результат An​ в массиве m, чтобы избежать повторных вычислений в будущем.

  if (n < MAX) m[n] = temp;
  return temp;
}

Основная часть программы. Читаем входные данные.

scanf("%lld %lld %lld %lld %lld",&n,&p,&q,&x,&y);

Вычисляем и выводим ответ.

res = f(n,p,q,x,y);
printf("%lld\n",res);
Eolymp #11319. Максимизируй это!

По заданным двум целым числам n и m найдите такое действительное число a, что значение f(1/2+a) является максимальным. Известно, что

f(t)=mini,j∈Z​∣ni​−mj​+t∣

Можно доказать, что максимальное значение функции f(t) рационально. Выведите его в виде несократимой дроби.

Вход. Содержит не более 104 тестов. Каждый тест состоит из одной строки, содержащей два целых числа n и m (1≤n,m≤109).

Выход. Для каждого теста выведите максимальное значение f(1/2+a) в виде несократимой дроби p/q, где q>0.

Пример входа
1 1
1 2
Пример выхода
1/2
1/4
Открыть задачу
Решение

Рассмотрим, какие значения принимает выражение f(i,j)=i/n−j/m, где i,j∈Z при фиксированных n и m. Например, f(n,m)=n/n−m/m=0.

Рассмотрим, например, функцию f(i,j)=i/3−j/5. Тогда

f(i,j)=155i−3j​

Согласно расширенному алгоритму Евклида, всегда существуют такие целые i и j, что i/3−j/5=1. Следовательно, наименьшим положительным значением, которое может принимать выражение i/3−j/5, является 1/15. Отсюда следует, что это выражение может принимать все возможные значения вида k/15, где k∈Z.

Теорема. Если f(i,j)=i/n−j/m, то эта функция принимает значения вида k/НОК(n,m), где k∈Z.

Доказательство. Действительно,

f(i,j)=i/n−j/m=n⋅mi⋅m−j⋅n​=(n⋅m)/НОД(n,m)(i⋅m)/НОД(n,m)−(j⋅n)/НОД(n,m)​=НОК(n,m)i⋅(m/НОД(n,m))−j⋅(n/НОД(n,m))​

Поскольку числа m/НОД(n,m) и n/НОД(n,m) являются взаимно простыми, то всегда существуют такие i и j, что числитель дроби равен 1. Следовательно, функция f(i,j) может принимать все возможные значения вида k/НОК(n,m), где k∈Z.

Теперь рассмотрим исходную функцию

f(t)=mini,j∈Z​∣ni​−mj​+t∣

Ее значения — расстояния от точки t до ближайшей точки вида k/НОК(n,m),k∈Z. Для максимизации функции f(t) значение t следует выбрать таким образом, чтобы оно находилось точно посередине между соседними точками k/НОК(n,m) и (k+1)/НОК(n,m), где k — целое число. Значение функции в такой точке равно 1/2⋅НОК(n,m), что и будет ответом.

Пример

Пусть f(t)=mini,j∈Z​∣3i​−5j​+t∣. Очевидно, что f(0)=0. Равенство f(1/15)=0 справедливо, так как существуют такие i и j, что i/3−j/5=−1/15 (например, i=1,j=2). Из этого также следует, что f(k/15)=0, где k∈Z.

Однако f(k/15+1/30)=1/30, и это будет наибольшим значением функции, так как точка k/15+1/30 находится на максимальном расстоянии от нулей функции.

Реализация алгоритма

Функция gcd вычисляет наибольший общий делитель чисел a и b.

long long gcd(long long a, long long b)
{
  return b == 0 ? a : gcd(b, a % b);
}

Функция lcm вычисляет наименьшее общее кратное чисел a и b.

long long lcm(long long a, long long b)
{
  return a / gcd(a, b) * b;
}

Основная часть программы. Читаем входные данные.

while (scanf("%lld %lld", &n, &m) == 2)
{

Вычисляем и выводим ответ.

  d = 2 * lcm(n, m);
  printf("%lld/%lld\n", 1, d);
}
Eolymp #11431. Встреча посередине

Задан массив из n целых чисел. Сколькими способами можно выбрать подмножество его элементов так, чтобы их сумма была равна s?

Вход. В первой строке заданы два целых числа: n (1≤n≤40) — размер массива и s (1≤s≤109) — требуемая сумма.

Во второй строке заданы n целых чисел: t1​,t2​,…,tn​ (1≤ti​≤109) — элементы массива.

Выход. Выведите одно число — количество способов выбрать подмножество элементов массива, сумма которых равна s.

Пример входа 1
4 5
1 2 3 2
Пример выхода 1
3
Пример входа 2
6 7
1 3 2 2 1 4
Пример выхода 2
8
Открыть задачу
Решение

Разобьем входной массив на две равные части: t1​,t2​,...,tn/2​ и tn/2+1​,tn/2+2​,...,tn​. Первую часть сохраним в массив a, вторую — в массив b.

Сначала решим задачу для последовательности a. Переберем все ее возможные подмножества (их будет не более 220). Для каждого подмножества вычислим сумму его элементов s, и для каждой такой суммы увеличим значение m[s] на 1. В качестве структуры данных m можно использовать, например, unordered_map. Таким образом, m[s] будет содержать количество способов выбрать подмножество из массива a с суммой элементов, равной s.

Затем переберём все подмножества массива b. Пусть sum — сумма элементов одного из таких подмножеств. Если объединить это подмножество со всеми подмножествами массива a, сумма элементов которых равна s−sum, то в результате получится подмножество исходного массива, сумма элементов которого равна s.

Пример

Рассмотрим второй пример. Разобьем входную последовательность на две части:

  • a=(1,3,2),

  • b=(2,1,4)

Переберем все подмножества множества a={1,3,2}:

{1}:sum=1,m[1]++;

{1,2}:sum=3,m[3]++;

{3}:sum=3,m[3]++;

{3,2}:sum=5,m[5]++;

{2}:sum=2,m[2]++;

{1,3,2}:sum=6,m[6]++;

{1,3}:sum=4,m[4]++;

{}:sum=0,m[0]++;

Структура m после обработки будет содержать следующие значения:

Теперь переберем все подмножества множества b={2,1,4}. В данном примере искомая сумма равна s=7. Для каждой суммы sum, соответствующей подмножеству b, будем прибавлять к ответу значение m[s−sum].

  • {2}:sum=2,m[7−2]=m[5]=1;

  • {1}:sum=1,m[7−1]=m[6]=1;

  • {4}:sum=4,m[7−4]=m[3]=2;

  • {2,1}:sum=3,m[7−3]=m[4]=1;

  • {2,4}:sum=6,m[7−6]=m[1]=1;

  • {1,4}:sum=5,m[7−5]=m[2]=1;

  • {2,1,4}:sum=7,m[7−7]=m[0]=1;

  • {}:sum=0,m[7−0]=m[7]=0;

Искомая сумма равна 1+1+2+1+1+1+1+0=8, что и является ответом.

Реализация алгоритма

Объявим рабочие структуры данных.

vector<int> a, b;
unordered_map<long long, long long> m;

Читаем входные данные. Первую половину последовательности заносим в массив a, вторую — в массив b.

scanf("%d %d", &n, &s);
a.resize(n / 2);
for (i = 0; i < n / 2; i++)
  scanf("%d", &a[i]);

b.resize(n - n / 2);
for (i = n / 2; i < n; i++)
  scanf("%d", &b[i - n / 2]);
C++
8 lines
179 bytes

Перебираем все подмножества последовательности a и вычисляем суммы их элементов. В структуре unordered_map сохраняем, сколько раз каждая сумма встречается среди этих подмножеств. Если sum — сумма текущего подмножества, то значение m[sum] увеличиваем на 1.

for (i = 0; i < (1 << a.size()); i++)
{
  sum = 0;

Переменная i содержит битовую маску текущего подмножества а. Сумма его элементов вычисляется в переменной sum.

      for (j = 0; j < a.size(); j++)
        if (i & (1 << j)) sum += a[j];

Для каждой найденной суммы sum увеличиваем значение m[sum] на 1.

  m[sum]++;
}

Количество способов получить сумму s из элементов исходного массива подсчитываем в переменной res.

res = 0;

Перебираем все подмножества последовательности b.

for (i = 0; i < (1 << b.size()); i++)
{
  sum = 0;

Переменная i содержит битовую маску текущего подмножества b. Сумма элементов этого подмножества вычисляется в переменной sum.

  for (j = 0; j < b.size(); j++)
    if (i & (1 << j)) sum += b[j];

Если объединить текущее подмножество множества b, сумма элементов которого равна sum, со всеми подмножествами множества a, сумма которых равна s−sum, то в результате получится подмножество исходного массива, сумма элементов которого равна s.

  if (m.count(s - sum) > 0) res += m[s - sum];
}

Выводим ответ.

printf("%lld\n", res);
Eolymp #4973. Мутация

Учёные генетики с планеты Олимпия снова проводят эксперименты с ДНК примитивных организмов. Геном организма представляет собой последовательность генов, каждый из которых можно закодировать с помощью одного натурального числа. Гены, которые кодируются одинаковыми числами, считаются одинаковыми, а гены, кодируемые различными числами, — разными.

Учёные уже создали примитивный организм и хотят модифицировать его геном таким образом, чтобы получить идеальный организм. Они считают, что в будущем это поможет разработать лекарства от множества болезней. Организм считается идеальным, если любые два одинаковых гена либо находятся на соседних позициях в геноме, либо между ними есть хотя бы один такой же ген.

За одну операцию учёные могут выбрать один или несколько одинаковых генов в геноме, удалить их, а затем вернуть их обратно в геном, возможно, на другие позиции. Поскольку каждая такая операция ослабляет организм, учёные стремятся минимизировать их количество при достижении цели.

Напишите программу, которая по заданному представлению генома определит наименьшее количество операций, необходимых для получения идеального организма.

Вход. Первая строка содержит количество генов n (1≤n≤105) в геноме примитивного организма. Во второй строке записано n натуральных чисел, каждое из которых не превышает n — последовательность генов в геноме.

Выход. Выведите наименьшее количество операций, необходимых для получения идеального организма.

Пример входа
1 2 1 3 1 3 2 4 5
Пример выхода
2
Открыть задачу
Решение

Перестановку генов следует выполнить таким образом, чтобы одинаковые гены располагались рядом. При этом необходимо минимизировать количество выполненных операций.

Каждому натуральному числу ai​ поставим в соответствие отрезок [si​;ei​], где si​ — позиция, на которой ai​ встречается впервые, а ei​ — позиция, на которой ai​ встречается в последний раз.

Пусть res — наибольшее возможное количество непересекающихся отрезков. Это значение можно найти с помощью алгоритма "выбора заявок", что означает, что каждый из оставшихся отрезков пересекается хотя бы с одним из выбранных. После этого нужно выполнить описанную процедуру над генами, которым эти отрезки соответствуют.

Если k — общее количество построенных отрезков. Тогда ответом на задачу будет значение k−res.

Пример

Для приведенного примера отрезки будут иметь вид (нумерация позиций начинается с нуля):

  • для 1:[0;4];

  • для 2:[1;6];

  • для 3:[3;5];

  • для 4:[7;7];

  • для 5:[8;8];

Отсортируем отрезки по координате конца:

[0;4],[3;5],[1;6],[7;7],[8;8]

Максимальное количество непересекающихся отрезков равно 3. Например, можно выбрать отрезки [0;4],[7;7],[8;8]. Для генов, соответствующих оставшимся отрезкам, следует выполнить процедуру, описанную в условии задачи. К таким генам относятся 2 и 3. Две перестановки можно выполнить, например, следующим образом:

Реализация алгоритма

Пусть MAX — максимальное возможное количество геномов во входной последовательности.

#define MAX 100001

Вектор пар v хранит отрезки [si​;ei​].

vector<pair<int, int> > v;
int s[MAX], e[MAX];

Читаем входные данные. Инициализируем si​=−1,ei​=−1.

scanf("%d", &n);
memset(s, -1, sizeof(s));
memset(e, -1, sizeof(e));

for (i = 0; i < n; i++)
{
  scanf("%d", &x);
C++
7 lines
122 bytes

Если число x встречается впервые, то его позицию во входной последовательности записываем в sx​.

  if (s[x] == -1) s[x] = i;

Если число x встречается не в первый раз, то его позицию записываем в ex​, считая, что x встречается в последний раз.

  if (i > e[x]) e[x] = i;
}

Все построенные отрезки заносим в вектор v.

for (i = 1; i <= n; i++) // numbers 1..n
  if (s[i] != -1) v.push_back(make_pair(e[i], s[i]));

Отрезки заносятся в вектор в обратном порядке (в виде [ei​;si​]), чтобы по умолчанию сортировка отрезков происходила в порядке возрастания их концов.

sort(v.begin(), v.end());

Находим максимальное количество res непересекающихся отрезков.

i = res = 0;
while (i < v.size())
{

Добавляем i-й отрезок во множество непересекающихся отрезков. Переменная temp содержит конец этого отрезка (назовём его текущим отрезком).

  res++; temp = v[i++].first; // end

Пока начало следующего отрезка меньше конца текущего, пропускаем такой отрезок, так как он пересекается с текущим.

  while (i < v.size() && v[i].second < temp) i++;
}

Выводим ответ.

printf("%d\n", v.size() - res);
Eolymp #2267. Путешествие

Армия Речи Посполитой направляется из города Кострома в деревню Домнино. Во главе армии стоят два гетмана — Стефан и Константин.

Стефан располагает картой Костромской области, на которой указаны все дороги между деревнями. Каждую ночь он ведёт армию от одной деревни к другой по одной из этих дорог. Константин, в свою очередь, раздобыл карту с секретными тропами и днём ведёт армию по одной из них. Перед каждым переходом каждый из гетманов спрашивает у проводника Ивана Сусанина, какой маршрут лучше выбрать.

На карте Стефана указана длина каждой дороги, поэтому он может определить кратчайшее расстояние от любой деревни до деревни Домнино. Аналогично, Константин знает кратчайшие пути по своей карте троп.

Иван Сусанин, являющийся тайным агентом, старается не вызывать подозрений. Поэтому каждый раз он выбирает такую дорогу (для Стефана) и такую тропу (для Константина), чтобы кратчайшее расстояние до деревни Домнино по соответствующей карте строго уменьшалось.

Помогите Ивану определить, какой максимальной длины может быть маршрут до деревни Домнино.

Вход. Первая строка содержит три целых числа n,s и t (2≤n≤1000,1≤s,t≤n) — количество деревень в Костромской области, номер начальной деревни и номер деревни Домнино соответственно. Деревни пронумерованы от 1 до n. Гарантируется, что s=t.

Затем следуют два блока, каждый из которых описывает карту: первый — карту Стефана, второй — карту Константина.

Первая строка каждого блока содержит целое число m (n−1≤m≤105) — количество дорог или троп. Каждая из следующих m строк содержит три целых числа a,b и l (1≤a,b≤n;1≤l≤106), описывающих соединение между деревнями a и b длиной l.

Передвигаться по дорогам и тропам можно в любом направлении. Гарантируется, что по каждой карте можно добраться из любой деревни в любую другую. Армия начинает движение вечером из деревни s, проходя одну дорогу за ночь и одну тропу за день.

Выход. Выведите одно целое число — максимально возможную длину маршрута до деревни Домнино (учитывая чередование дороги и тропы). Если Иван Сусанин может водить армию бесконечно, не приводя её в Домнино, выведите "−1".

Пример входа 1
5 1 5
5
1 2 2
1 4 2
2 3 1
3 4 1
5 3 1
4
1 2 2
2 4 2
2 3 1
2 5 2
12 lines
76 bytes
Пример выхода 1
-1
Пример входа 2
3 1 3
4
1 2 10
2 3 10
1 3 20
2 3 30
4
2 1 10
1 3 10
1 1 10
2 3 10
11 lines
77 bytes
Пример выхода 2
20
Открыть задачу
Решение

Построим два графа: граф дорог g[0] и граф троп g[1]. Далее вычислим кратчайшие расстояния от деревни Домнино t до всех деревень:

  • По карте Стефана: d[0][i] — минимальное расстояние от t до деревни i;

  • По карте Константина: d[1][i] — минимальное расстояние от t до деревни i;

Затем рассмотрим граф, вершинами которого являются пары (v,parity):

  • v — номер деревни;

  • parity=0 (ночь, передвижение по дорогам), parity=1 (день, передвижение по тропам).

Армия начинает путь вечером из начальной деревни, двигаясь ночью по дороге (parity=0). Запускаем поиск в глубину из вершины (s,0) по следующим правилам:

  • из (v,0) (ночь) идём по дороге в (u,1), если d[0][u]<d[0][v];

  • из (v,1) (день) идём по тропе в (u,0), если d[1][u]<d[1][v].

Находясь в состоянии (v,parity), перебираем только допустимые переходы, то есть такие, при которых расстояние до Домнино строго уменьшается. Среди них ищем путь максимальной длины.

Напомним, что каждый из гетманов стремится двигаться так, чтобы расстояние по его карте уменьшалось (при этом он не обязан следовать кратчайшему пути).

При выполнении поиска в глубину будем поддерживать следующую динамику:

  • dp[type][v] — максимальная длина пути от v до t, начиная с типа type.

Для каждого допустимого перехода:

  • Рекурсивно вызываем dfs(to,type⊕1) — переход в противоположную фазу.

  • Обновляем значение dp[type][v]:

dp[type][v]=max(dp[type][v],dp[type⊕1][to]+weight);

Таким образом, мы выбираем наилучший следующий ход, который приводит к максимальной суммарной длине пути. Это аналог стандартного поиска самого длинного пути в DAG (ориентированном ациклическом графе), где направление рёбер определяется уменьшением кратчайшего расстояния.

Движение разрешено только по рёбрам, ведущим к вершинам с меньшим расстоянием до Домнино. Однако в задаче присутствуют два графа – два типа рёбер: дороги и тропы, – а переходы между ними чередуются: ночь (дорога), день (тропа), ночь, день и так далее. Именно из-за этого чередования карт (дорог и троп) может возникнуть цикл между вершинами, даже несмотря на то, что в каждом отдельном графе (по одной карте) движение всегда идёт по убывающему расстоянию.

Если при поиске в глубину мы вновь попадаем в вершину, которая уже находится в стеке обработки, это означает, что обнаружен цикл.

Реализация алгоритма

Объявим константы.

#define INF 2e18
#define MAXN 1005

Объявим используемые структуры данных:

  • g[0] и g[1] — списки смежности для графов дорог и троп соответственно.

  • d[0] и d[1] — кратчайшие расстояния от деревни Домнино t до всех остальных деревень.

  • dp[0] и dp[1] — максимальная длина пути от вершины v до t, если стартовать из v по дороге (type=0) или по тропе (type=1).

  • used[0] и used[1] — метки вершин при обходе в глубину для графов дорог и троп соответственно.

vector<pair<int, int>> g[2][MAXN];
long long d[2][MAXN];
long long dp[2][MAXN];
int used[2][MAXN];

Функция dijkstra вычисляет кратчайшие расстояния от деревни Домнино t до всех остальных деревень либо по дорогам (type=0), либо по тропам (type=1).

void dijkstra(int type)
{
  for (int i = 1; i <= n; i++)
    d[type][i] = INF;

  d[type][t] = 0;

  priority_queue<pair<long long, int>, vector<pair<long long, int>>, greater<pair<long long, int>>> pq;
  pq.push({ 0, t });

  while (!pq.empty())
  {
    pair<long long, int> top = pq.top();
    pq.pop();

    long long dist = top.first;
    int v = top.second;

    if (dist > d[type][v]) continue;

    for (auto edge : g[type][v])
    {
      int to = edge.first;
      int w = edge.second;

      if (d[type][to] > d[type][v] + w)
      {
        d[type][to] = d[type][v] + w;
        pq.push({ d[type][to], to });
      }
    }
  }
}
C++
33 lines
673 bytes

Функция dfs выполняет поиск в глубину и находит самый длинный путь при движении по чередующимся картам, начиная из деревни v по дороге (type=0) или по тропе (type=0). Если при таком чередующемся движении возникает возможность попасть в цикл, функция возвращает −1.

void dfs(int v, int type)
{

Вершина v в графе g[type] становится серой.

  used[type][v] = 1;

Инициализируем динамику.

  dp[type][v] = 0;

Перебираем ребра, исходящие из вершины v в графе g[type].

  for (auto edge : g[type][v])
  {
    int to = edge.first;
    int weight = edge.second;

Переход из вершины v в вершину to возможен только в том случае, если расстояние от to до Домнино меньше, чем расстояние от v до Домнино.

    if (d[type][to] < d[type][v])
    {

Если вершина to в графе g[type⊕1] уже находится в процессе обхода (помечена как "серая"), значит, обнаружен цикл — возвращаем −1.

      if (used[type ^ 1][to] == 1)
      {
        cout << -1 << endl;
        exit(0);
      }

Если вершина to в графе g[type⊕1] еще не посещена, продолжаем из нее поиск в глубину.

      if (used[type ^ 1][to] == 0)
        dfs(to, type ^ 1);

После обработки всех допустимых переходов пересчитываем динамику, выбирая максимальную длину пути от вершины v в графе g[type] до Домнино.

      dp[type][v] = max(dp[type][v], dp[type ^ 1][to] + weight);
    }
  }

Вершина v в графе g[type] помечается черной.

  used[type][v] = 2;
}

Основная часть программы. Читаем входные данные. Строим графы дорог и троп.

scanf("%d %d %d", &n, &s, &t);
scanf("%d", &m1);
for (i = 0; i < m1; i++)
{
  scanf("%d %d %d", &a, &b, &len);
  g[0][a].push_back({ b, len });
  g[0][b].push_back({ a, len });
}

scanf("%d", &m2);
for (i = 0; i < m2; i++)
{
  scanf("%d %d %d", &a, &b, &len);
  g[1][a].push_back({ b, len });
  g[1][b].push_back({ a, len });
}
C++
16 lines
344 bytes

Вычисляем кратчайшие расстояния от деревни Домнино t до всех остальных деревень — отдельно для карты дорог и для карты троп.

dijkstra(0);
dijkstra(1);

При помощи поиска в глубину находим самый длинный путь при движении по чередующимся картам, начиная из деревни s по дороге.

dfs(s, 0);

Выводим ответ. Стартовая точка — деревня s, движение начинается по дороге.

printf("%lld\n", dp[0][s]);
Eolymp #11320. Удивительный трюк

Алиса — фокусница, и она придумала новый трюк. У неё есть n карточек с различными числами от 1 до n, написанными на них. Сначала она просит одного из зрителей перетасовать колоду и выложить карты в ряд. Допустим, что на i-й карте слева стоит число ai​.

Затем Алиса выбирает две перестановки p и q. На p и q есть ограничение — у перестановок не должно быть фиксированных точек. То есть ∀i:pi​=i и qi​=i.

После выбора перестановок Алиса перемешивает карты в соответствии с ними. Теперь i-ой картой слева является карта a[p[q[i]]]. Трюк считается успешным, если на i-ой карте слева после тасования стоит число i.

Помогите Алисе выбрать перестановки p и q или скажите, что это невозможно для заданной перестановки a.

Вход. Первая строка содержит количество тестов t (1≤t≤105).

Каждый тест задается двумя строками. Первая строка содержит одно целое число n (1≤n≤105) — количество карточек. Вторая строка содержит n целых чисел ai​ (1≤ai​≤n,∀i=j:ai​=aj​) — начальную перестановку карточек.

Гарантируется, что сумма n по всем тестам не превышает 105.

Выход. Выведите ответ для каждого теста в том же порядке, в котором они появляются во входных данных.

Для каждого теста в отдельной строке выведите "Impossible", если решение не существует.

В противном случае, в первой строке выведите "Possible", а в следующих двух строках выведите перестановки p и q.

Пример входа
4
2
2 1
3
1 2 3
4
2 1 4 3
5
5 1 4 2 3
9 lines
47 bytes
Пример выхода
Impossible
Possible
3 1 2
2 3 1
Possible
3 4 2 1
3 4 2 1
Possible
4 1 2 5 3
3 1 4 5 2
10 lines
96 bytes
Открыть задачу
Решение

Рассмотрим три перестановки: a,p,q. Пусть I — тождественная перестановка. Рассмотрим равенство: a(p(q(I)))=I. Отсюда p(q(I))=a−1. Используя тот факт, что перестановка применяется справа налево, указанное тождество можно переписать в виде: p°q=a−1. Отсюда следует что q=p−1°a−1.

Генерируем случайную перестановку p, у которой нет фиксированных точек. После чего вычисляем перестановку q=p−1°a−1 и тоже проверяем, чтобы у нее не было фиксированных точек. Найденные перестановки p и q являются искомыми.

Пример

Рассмотрим перестановку a=(5,1,4,2,3).

Обратной к ней будет a−1=(2,4,5,3,1).

Генерируем случайную перестановку p без фиксированных точек. Пусть, например, p=(4,1,2,5,3). Вычислим перестановку, ей обратную: p−1=(2,3,5,1,4).

Далее вычисляем q=p−1°a−1=(2,3,5,1,4)°(2,4,5,3,1)=(3,1,4,5,2). Перестановка q не содержит фиксированных точек, поэтому она нам подходит.

Реализация алгоритма

Функция print выводит элементы массива a, начиная с первого.

void print(vector<int>& a)
{
  for (int i = 1; i < a.size(); i++)
    printf("%d ", a[i]);
  printf("\n");
}

Функция is_valid проверяет, содержит ли перестановка v фиксированные точки.

bool is_valid(vector<int>& v)
{
  for (int i = 1; i < v.size(); i++)
    if (v[i] == i) return false;
  return true;
}

Объявим рабочие массивы.

vector<int> a, p, p1, q;

Основная часть программы. Читаем входные данные.

scanf("%d", &tests);
while (tests--)
{
  scanf("%d", &n);

Инициализируем массивы.

  a.resize(n + 1);
  p1.resize(n + 1);
  p.resize(n + 1);
  q.resize(n + 1);

Читаем входную перестановку a и сразу в массив а заносим перестановку, ей обратную. То есть массив a содержит перестановку a−1, где a — входная перестановка.

  for (i = 1; i <= n; i++)
  {
    scanf("%d", &x);
    a[x] = i;
  }

mt19937 — это один из стандартных генераторов псевдослучайных чисел в C++ (Mersenne Twister с периодом 19937). Он очень быстро работает и обладает хорошими статистическими свойствами, что делает его отличным выбором для большинства задач, требующих генерации случайных чисел.

  mt19937 gen(random_device{}());

В переменной found будем отмечать, найден ли ответ для текущего теста.

  found = false;

Проводим 1000 итераций.

  for (it = 0; it < 1000; it++)
  {

Генерируем случайную перестановку. Для этого заносим в массив p числа 0,1,2,... и при помощи функции shuffle проводим перемешивание элементов, начиная с первого (не нулевого) элемента. Мы работаем с перестановками от 1 до n.

    iota(p.begin(), p.end(), 0);
    shuffle(p.begin() + 1, p.end(), gen);

Проверяем, является ли перестановка p допустимой (не содержит фиксированных точек).

    if (!is_valid(p)) continue;

Вычисляем перестановку p1​=p−1.

    for (i = 1; i <= n; i++)
      p1[p[i]] = i;

Вычисляем перестановку q=p1​°a−1=p−1°a−1.

    for (i = 1; i <= n; i++)
      q[i] = p1[a[i]];

Если перестановка q является допустимой, то выводим ответ.

    if (is_valid(q))
    {
      puts("Possible");
      print(p);
      print(q);
      found = true;
      break;
    }
  }
C++
9 lines
135 bytes

Если за 1000 итераций ответ не был найден, то его не существует.

  if (!found) puts("Impossible");
}

12

Комментарии

Загрузка

Момент, получаем данные с сервера

Пока что ничего

Будьте первым, кто начнет обсуждение!
Войти