Блог пользователя tourist

Автор tourist, 14 лет назад, По-русски
Обсуждение контеста

Задача A. Проблема Нольдбаха

В данной задаче нужно было уметь искать простые числа в диапазоне от 2 до N. При этом ограничения позволяли искать их любым способом - как решетом Эратосфена, так и перебором всех возможных делителей числа.
Возьмём каждую пару соседних простых чисел и проверим, является ли их сумма, увеличенная на 1, простым числом. Найдём количество таких пар, сравним с числом K и выведем ответ.

Задача B. Иерархия

Заметим, что если у каждого человека, кроме одного, будет ровно один начальник, то полученная иерархия будет древовидной.
Рассмотрим для каждого человека все заявления, в которых он фигурирует как подчинённый. Если более чем для одного человека таких заявлений не существует вообще, очевидно, что ответ -1. В противном случае, найдём минимальную стоимость такого заявления для каждого человека и сложим эти стоимости. Это и будет ответ.
Также можно было применить алгоритм Крускала для нахождения минимального остовного дерева в графе. Однако перед добавлением каждого ребра в дерево необходимо добавить проверку того, что ни у одного сотрудника не появится больше одного начальника.
И да, значения квалификаций сотрудников не были нужны для того, чтобы решить задачу :)
Понятие квалификации вводилось для того, чтобы обеспечить отсутствие циклов.

Задача C. Баланс

Рассмотрим входную строку A длиной n. Проделаем некоторые операции из условия над этой строкой; допустим, мы получили строку B. Сжатием строки X будем называть такую строку X, полученную из X путём замены всех подряд идущих одинаковых букв на одну такую же букву. Например, для строки S = “aabcccbbaa, её сжатие S’ = “abcba.
Рассмотрим сжатия строк A и B - A и B. Можно заметить, что если строку B можно получить из строки A через некоторые операции из условия, то B является  подпоследовательностью A', и наоборот - если для любых строк A и B равной длины B' является подпоследовательностью A', то строку B можно получить из строки A через некоторые операции из условия.
Интуитивно это можно обосновать так. Допустим, мы использовали какую-то букву a на позиции i в строке A для того, чтобы расставить букву a на позициях j..k строки B (путём использования операций из условия). Тогда мы не могли использовать буквы на позициях в строке A, меньших i, для того, чтобы каким-то образом повлиять на буквы k + 1..n строки B; аналогично, мы не могли использовать буквы на позициях в строке A, больших i, для того, чтобы повлиять на буквы 1..j - 1 строки B.

Теперь у нас есть некоторая основа для нашего решения, которое будет заключаться в применении динамического программирования. Будем составлять строку B посимвольно, учитывая тот факт, что сжатие B' должно оставаться подпоследовательностью сжатия A' (попутно с составлением B будем искать сжатие B' в A'). Для этого будем хранить позицию i в строке A', означающую, на какой позиции остановился поиск B' в A', и три параметра kA, kB, kC, означающие соответственно размер множества каждой из букв a, b, c в строке B. Переходы в этом рекуррентном соотношении следующие:
1) Следующий символ строки B - это a. Тогда переход произойдёт из состояния (i, kA, kB, kC) в (nexti, 'a', kA + 1, kB, kC).
2) Следующий символ строки B - это b. Тогда переход произойдёт из состояния (i, kA, kB, kC) в (nexti, 'b', kA, kB + 1, kC).
3) Следующий символ строки B - это c. Тогда переход произойдёт из состояния (i, kA, kB, kC) в (nexti, 'c', kA, kB, kC + 1).
Здесь nexti, x - это минимальное такое j, что j ≥ i и A'j = x (то есть ближайшее, начиная с позиции i, вхождение символа x в A'). Естественно, если для какого-то случая значение nexti, x не определено, то переход невозможен.
Подсчитав рекуррентную функцию f(i, kA, kB, kC), ответ найти несложно: для всех троек kA, kB, kC, для которых выполняется условие сбалансированности.

Такое решение не укладывается в ограничение по памяти и по времени. Заметим, что если строка B собирается быть сбалансированной, то должно выполняться условие kA, kB, kC ≤ (n + 2) / 3. Таким образом, количество состояний в рекуррентном соотношении уменьшилось приблизительно в 27 раз. Однако можно ещё в несколько раз уменьшить количество используемой памяти, если заметить, что во всех переходах значение kA (для этой цели можно взять и kB, и kC) изменяется либо на 0, либо на 1. Значит, можно хранить матрицу значений f по слоям (где слой задаётся некоторым значением kA).

Сложность решения O(N4) с достаточно маленькой скрытой константой.

Задача D. Блокнот


Ответ на задачу равен bn - 1 * (b - 1) mod c. Главное, что нужно уметь делать в этой задаче - считать значение AB mod C для некоторых длинных чисел A и B и короткого числа C. Простой алгоритм быстрого возведения в степень не проходит ограничение по времени по той причине, что для того, чтобы перевести число B в двоичную систему, потребуется порядка O(|B|2) операций, где |B| - количество цифр в десятичной записи числа B.
Первое, что нужно сделать - превратить A в A mod C. Несложно понять, что такое преобразование не меняет ответ.
Представим C в виде C = p1k1p2k2...pmkm, где pi - различные простые числа.
Посчитаем ti = AB mod piki для всех i следующим образом. Здесь есть два случая:
1) A mod pi не равно 0: тогда A и pi взаимно просты, и можно применить теорему Эйлера: AB' = AB (mod C), где B' = B mod φ(C), а φ(C) - это функция Эйлера;
2) A mod pi равно 0: тогда при B ≥ ki будет выполняться ti = AB mod piki = 0, а при B < ki можно тривиально посчитать это значение.
Так как все piki попарно взаимно просты, то можно применить китайскую теорему об остатках для подсчёта результата.

Также можно применить теорему Эйлера напрямую. Заметим, что для некоторых B1, B2 ≥ 29 выполняется |B1 - B2| mod φ(C) = 0, то все значения ti для них будут совпадать (29 - это максимальное возможное значение ki). Используя это, можно уменьшить число B до относительно небольших размеров и посчитать ответ алгоритмом быстрого возведения в степень.

Есть ещё одно решение, применявшееся многими участниками. Представим число B в виде B = d0 + 10d1 + 102d2 + ... + 10mdm, где 0 ≤ di ≤ 9 (фактически di - это i-я с конца цифра числа B). Так как ax + y = ax· ay, получаем, что AB = Ad0· A10d1· ...· A10mdm. Значения A10i можно узнать последовательным возведением в степень 10; зная A10i, можно узнать и A10idi возведением первого в степень di. Ответ - произведение всех множителей (разумеется, по модулю C).

Задача E. Палисечение

Первое, что нужно сделать - найти все подпалиндромы в заданной строке. Для этого можно применить красивый алгоритм, описанный на сайте e-maxx.ru/algo. Вкратце, результатом работы этого алгоритма являются максимальные возможные длины подпалиндромов, центры которых находятся либо в позиции i строки, либо между позициями i и i + 1, для всех таких возможных позиций центра.
Для примера, предположим, что максимальная длина подпалиндрома с центром в позиции i равна 5; это значит, что центр в позиции i находится у трёх подпалиндромов: длины 1 (i..i), 3 (i - 1..i + 1) и 5 (i - 2..i + 2). Получаем, что все возможные начала подпалиндромов лежат на отрезке i - 2..i. В общем случае начала и концы подпалиндромов с фиксированным центром будут занимать некоторый отрезок позиций, который можно легко найти.
Найдём для каждой позиции i строки величину starti, равную количеству подпалиндромов, начало которых находится в позиции i. Все эти значения можно найти за линейное время. Для этого заведём вспомогательный массив ai. Если после обработки некоторой позиции выяснилось, что какие-то новые подпалиндромы начинаются в позициях i..j, то значение ai увеличим на 1, а значение aj + 1 уменьшим на 1. Теперь . Обоснование этого факта оставляется читателю в качестве упражнения :)
Аналогичным образом можно вычислить finishi - количество подпалиндромов, конец которых находится в позиции i.
Так как можно несложно подсчитать общее количество подпалиндромов, найдём количество пар непересекающихся подпалиндромов и вычтем это количество из общего количества пар. Заметим, что если два подпалиндрома не пересекаются, то один из них лежит в строке строго левее другого. Количество пар непересекающихся подпалиндромов можно подсчитать по формуле:

Эту сумму можно найти за линейное время, если значение при переходе от i к i + 1 пересчитывать добавлением finishi к уже накопленной сумме.
Таким образом, сложность решения O(N).
Разбор задач Codeforces Beta Round 17
  • Проголосовать: нравится
  • +30
  • Проголосовать: не нравится

14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
В задаче C хорошо бы отметить что конечной строкой может являться любая строка которая после сжатия даст подпоследовательность исходной строки, так по-моему легче понять эту динамику.
  • 14 лет назад, # ^ |
      Проголосовать: нравится +1 Проголосовать: не нравится
    "и наоборот - если для каких-то строк A и B B' является подпоследовательностью A', то строку B можно получить из строки A через некоторые операции из условия"
    По-моему, эта фраза это и обозначает, но всё же добавил пару слов.
  • 14 лет назад, # ^ |
      Проголосовать: нравится +1 Проголосовать: не нравится
    Да, многовато копий получилось. А я-то думал, что если комментарий не хочет добавляться, а потом я обновляю страницу и на ней нет комментария, то там его действительно нет :)

    Удалите, пожалуйста, лишние копии.
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
А у участников в E прошло N log N? И линейно её тоже сдали?
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
в задаче D в последнем решении AB должно быть произведением, а не суммой, нет?:)
14 лет назад, # |
  Проголосовать: нравится +2 Проголосовать: не нравится
Вообще, если на Yandex-Open задачи типа "завтрак туриста", то это наверное ужин :)
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Excellent analysis. Thank you.
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

But what can you say about my solution?

In fact, it looks like:

a %= c;

p = phi(c);

ans = (a ^ (b % p)) % c;

if (b >= p && ((a ^ p) % c) == 0)

   ans = 0;

Maybe there is some subtle counter-example?

  • 14 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    What? Are you talking about submit 63341? It's not equivalent to what you wrote.
  • 14 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    If your solution is as you wrote, then I believe it's wrong, at least for the case a = 2, b = 4, c = 12. Your answer is 1 while the correct answer is 2^4 mod 12 = 4.
    • 14 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится

      Ah, of course, my bad memory :)

      a %= c;

      p = phi(c);

      ans = (a ^ (b % p)) % c;

      if (b >= p)

         ans *= ((a ^ p) % c);

14 лет назад, # |
  Проголосовать: нравится +13 Проголосовать: не нравится
Thanks so much for this analysis! Really appreciated, and I hope you will repeat it in upcoming contests.
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
In D, simplest generalization of fast exponentiation works well: A^(10*x+y) = (A^10)^x * A^y
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
No luck on problem C. I used java and got "exceed memory limitation error" when allocating dp[151][52][52][52]. :|
  • 14 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    I use dp[kA+kB+kC][151][kA][kB], it sounds worse than your way. However, kA+kB+kC always increases by one in each step of DP, so you can use dp1[151][kA][kB] and dp2[151][kA][kB] instead. It's easy to fit in memory.
  • 14 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    You need to allocate a ragged array, like this:

    int[][][][] dp = new int[151][52][][];
    for (int i = 0; i < dp.length; ++i) {
        for (int j = 0; j < 52; ++j) {
            dp[i][j] = new int[52 - j][];
            for (int k = 0; j + k < 52; ++j) {
                dp[i][j][k] = new int[52 - j - k];
            }
        }
    }

    This allocates only the elements actually needed by the solution, so fitting in memory limit.

    C/C++ users may use "full" allocation - unused pages of memory will not be loaded, and ML will not be overcomed. But use it with care - there are some problems (not this one) using "lazy" dynamic programming where it is possible to load all or just too many pages by some tricky tests, even if the number of used cells is acceptable.
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Кстати, почему в задаче B - не работает алгоритм Прима?
  • 14 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    Прима мог получить 2 босса(2 корня), ты этот случай рассмотрел?
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
A naive 15-line python code gives "runtime error on case 24" on problem D, but I expected something like TLE. What could be the reason for it? I usually code in C++ and I don't know what can cause a runtime error on python.
http://pastie.org/private/qsj8vbefe3flrfyuqi6ffq

Thank you all!
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

The tutorial is nice.
Will you post the tutorials for coming contests as well?

14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Не могли бы выложить тест 24 для задачи Е?? Почему то WA стабильно получаю, долго мучаюсь...
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Тест не маленький, поэтому выложил на ФО по-быстрому.
TEST24_E
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Спасибо, разобрался.
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Why can't i view the problem statement in english................................
14 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Excellent analysia...
14 лет назад, # |
Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится
Здравствуйте. Не могу понять, почему программа задачи B в моей реализации не проходит по времени. TL 17, причем на предыдущих тестах стабильно показывала 10 мс.. Не могу понять, в чем может быть баг, поэтому привожу текст программы полностью
#include "stdafx.h"
#pragma comment(linker, "/STACK:16777216")
#include <iostream>
using namespace std;

int main()
{
    long n, q[1000], a[1000], b[1000], c[1000], m, min_st[1000];
    int IsUch[1000], IsValid=0, i;
    cin>>n;
    for(i=0;i<n;i++)
    {
        cin>>q[i];
        IsUch[i]=0;
        min_st[i]=LONG_MAX-1;
    }
    cin>>m;
    for(i=0;i<m;i++)
    {
        cin>>a[i]>>b[i]>>c[i];
        if(q[a[i]-1]>q[b[i]-1])
        {
            IsUch[b[i]-1]=1;
            min_st[b[i]-1]=min(min_st[b[i]-1], c[i]);
        }
    }
   
    for(i=0;i<n;i++)
        IsValid+=(IsUch[i]==0?1:0);
    if(IsValid>1)
    {
        cout<<-1<<endl;
        return 0;
    }
    long min_sum=0;
    for(i=0;i<n;i++)
        min_sum+=min_st[i]!=(LONG_MAX-1)?min_st[i]:0;
    cout<<min_sum<<endl;
    return 0;
}
  • 14 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится
    M<=10000, маловаты массивы, поставил больше, прошло. 
»
5 лет назад, # |
Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

Blog not visible tourist

Unable to parse markup [type=CF_HTML]

»
4 года назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

that was very nice catch that employee qualification was not necessary.. but it's tricky at the same time

»
11 месяцев назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

omg tourist round

»
5 месяцев назад, # |
  Проголосовать: нравится -6 Проголосовать: не нравится

=) I can't solve this =)