Java → Разбор Chandelier 1

 
1
 

Всем привет! Так как месяц java еще не закончился, решил сделать разбор на эту задачу, так как она очень красиво решается использованием полиморфизма...

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

Для начала, определим основной интерфейс:

interface Item{
    public abstract int getStackSize();
    public abstract String getText();
}

Как видим любой унаследованный класс должен будет реализовывать два метода:

  1. getStackSize() - должен возвращать минимальный размер стека, необходимый для построения элемента.
  2. getText() - должен выдавать итоговую строку - порядок выполнения.

В нашей программе будут два класса, наследующиеся от Item класса. Это PointItem и RingItem.

Класс для точечного элемента PointItem очень прост... Его метод getStackSize() всегда возвращает 1, так элемент там только один, а getText() возвращает соответственно "а".

Теперь рассмотрим класс RingItem:
Во-первых, в классе есть три переменных:

1. int size - количество элементов в кольце.    
2. Item[] items - элементы лежащие в кольце.    
3. int offset - номер элемента, с которого надо начинать строить кольцо для оптимального использования стека.

Во-вторых, класс имеет один конструктор, который принимает количество элементов в нем и парсит входную строку... Кстати, входная строка хранится в классе Main в виде статичных массива символов и поинтера на последний элемент... Почему на последний? Потому что в конце у нас остается 1 элемент и от него проще построить всю структуру... Итак, получаем следующую реализацию конструктора:

    public RingItem(int t){
        items = new Item[t];
        size = t;
        for (int i = 0; i < t; i++)
            if (Main.input[--Main.pointer] == 'a') items[t-i-1] = new PointItem();
            else items[t-i-1] = new RingItem(Main.input[Main.pointer]-'0');
    }

Ну и перейдем к основным методам. Во-первых, мы выполняем функцию getStackSize(), где сначала находим рекурсивно стеки всех текущих элементов, а затем обычным перебором находим оптимальный offset. Получается вот такой вот метод:

    public int getStackSize() {
        int[] stacks = new int[size];

        for (int i = 0; i < size; i++)
            stacks[i] = items[i].getStackSize();

        int min = Integer.MAX_VALUE;
        for (int i = 0; i < size; i++){
            int k = 0;
            for (int j = 0; j < size; j++)
                k = Math.max(k, stacks[(i+j)%size]+j);
            if (k < min) {min = k; offset = i;}
        }

        return min;
    }

Во-вторых, вызываем метод getText, который для найденного отступа возващает нужную строку... Опять же рекурсивно:

    public String getText() {
        StringBuffer s = new StringBuffer("");
        for (int i = 0; i < size; i++)
            s.append(items[(offset+i)%size].getText());
        s.append(Integer.toString(size));
        return s.toString();
    }    

В итоге при готовой структуре мы получаем вполне красивый код в Main классе:

public class Main{
    public static char[] input;
    public static int pointer = 0;
    public static void main(String args[]) throws  Exception {
        BufferedReader in = new BufferedReader(new FileReader("chandelier.in"));
        PrintWriter out = new PrintWriter(new File("chandelier.out"));

        input = in.readLine().toCharArray();
        pointer = input.length;

        RingItem item = new RingItem(1);
        out.println(item.getStackSize());
        String res = item.getText();
        out.print(res.substring(0, res.length()-1));

        out.close();
    }
}

Теория графов → Разбор Лабиринт 1

 
1
 

Для начала определим цель задачи: найти путь минимальной длины из одной точки в графе до другой. Теперь определим, что нам дано:
1. Ориентированный граф из N вершин и M ребер.
a. Дуги в графе могут иметь как положительные, так и отрицательные веса.
b. Граф может содержать циклы (как положительные, так и отрицательные)
c. Граф может быть не связным.
d. Между двумя вершинами может быть сколь угодно большое количество ребер.
e. Граф может содержать петли.
2. Начальная и конечная вершины – A и B.
3. Начальный баланс, необходимый для прохождения проходов – P.
a. Баланс не может быть отрицательным. Следовательно, нельзя пройти через проход при нехватке баланса.

За основу возьмем алгоритм Беллмана-Форда (Поиск минимального пути в графе с отрицательными дугами). Алгоритм состоит из двух частей:
1. Поиск минимальных путей содержащих максимум N-1 дуг, то есть, возможно, не содержащих отрицательных циклов.
2. Поиск отрицательных циклов в графе.

Для реализации первой части алгоритма будем использовать матрицу G[N][N], где элемент G[i][j] – максимальный баланс, оставшийся после прохождения из начальной вершины в i-ую с использованием максимум j дуг (равен -1, если до вершины нельзя дойти, используя j дуг). Естественно, для j = 0 G[A][0] = P, G[i!=A][0] = -1. Далее, N-1 раз перебираем все ребра в графе и проверяем, могут ли они увеличить путь для какой-либо вершины. Так как нужно перебирать все ребра, будем хранить их в массиве E[M][3], где E[i][0] – вершина-начало дуги, E[i][1] – вершина-конец дуги и E[i][2] – вес дуги. Получаем следующий цикл:

for i := 1 -> N-1 do begin    
  for j := 0 -> M-1 do begin    
    if (G[ E[j][0] ][ i-1 ] >= 0 and G[ E[j][0] ][ i-1 ] – E[j][2] >= 0) then    
    G[E[j][1]][i] = MAX(G[E[j][0]][i-1] – E[j][2], G[E[j][1]][i], G[E[j][1]][i-1]);    
  end;    
end;    

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

boolean flag = true;    
int[] last := copy_of_G[][N-1];    
while (flag) begin    
  flag = false;    

  int[] new := copy_of_last;    
  for j := 0 -> M-1 do begin    
    if (last[ E[j][0] ] >= 0 and     
        last[ E[j][0] ] – E[j][2] >= 0) then    
    new[E[j][1]][i] = MIN(MAX(last[E[j][0]] – E[j][2], new[E[j][1]]), INFINITY);    
  end;    

  for i := 0 -> N-1 do begin    
    if (new[i] > last[i]) then begin    
      flag := true; new[i] := INFINITY;    
    end;    
  end;    

  last := copy_of_new;    
end;    

В конце концов, массив last содержит максимальный баланс для каждой вершины с учетом всех отрицательных циклов. А last[B] – результат, который нам нужен.

ejudgemanager.analyses → Analysis Ход конем 1

 
0
 

Хорошая задачка на динамику и длинную арифметику... Создаем матрицу A[n][10] типа BigInteger. Элемент A[i][j] - количество номеров длины i, заканчивающихся на цифру j. Далее зная количество номеров длины i и то, что после 0 может идти 4 или 6, после 1 - 6 или 8 и т.д. мы можем получить количество номеров длины i+1 заканчивающихся на определенную цифру... в конце концов просто складываем все эдементы строки n. это и будет ответ. P.S. для удоюства можно завести матрицу смежности B[10][10], которая будет показывать какую цифру после какой можно добавить...

ejudgemanager.analyses → Analysis Заправки 1

 
0
 

Здесь просто реализация алгоритма Дейкстры. Длина пути из одного города в другой будет просто стоимость бака в первом городе...

ejudgemanager.analyses → Analysis Столовские Котлеты 1

 
1
 

Интересная задачка на динамику... Создаем матрицу (N+1)x(N+1), в которой элемент A[i][j] - это количество способов раазложить число i на сумму различных чисел с максимальным слагаемым меньше либо равным j. Для j > i, естественно, A[i][j] = A[i][i], так как мы не сможем записать i в виде суммы с числом больше i.

  • Первым шагом будет A[0][j] = 1 для всех j от 0 до n.

  • Далее... Число i можно представить в виде суммы с
    максимальным слагаемым меньше j, либо с максимальным слагаемым равным j. В первом случае это просто количество вариантов расписать число i на сумму с максимальным слагаемым j-1, то есть A[i][j-1]. Во втором случае мы фиксируем число j, и соответственно раскладываем число i-j на сумму с максимальным слагаемым j-1, то есть A[i-j][j-1].

  • Отсюда получаем отношение A[i][j] = A[i][j-1] + A[i-j][j-1].

  • Выполняем это отношение для всех элементов матрицы. Получаем результат A[n][n].

ejudgemanager.analyses → Analysis XOR 2

 
-1
 
  • Во-первых, после считывания генерируем матрицу бинарных представлений введеных чисел (не важно в каком порядкеб главное чтоб количество единиц в каждом числе совпадало). При этом сразу определяем в каких столбцах четное количество единиц, а в каких нет.

  • Далее пробегаем по всем столбцам сначала слева направо. Для каждого столбца с нечетным количеством единиц ищем елемент столбца справа, в котором эти столбцы отличаются, и соответственно меняем эти элементы местами. Также меняем четность каждого стобца. Если же элемент найти не удалось, то просто игнорируем стоблец. Таким образом мы уравняли столбцы слева.

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

Теория графов → Analysis Kingdom of Magic 1

 
-1
 

Задача на алгоритм Дейкстры с дополнительными условиями... Первое условие две точки всегда соединены проходом... Второе - через один проход перемещаться одновременно нельзя...

Есть два пути:

  1. Реализавывать алгоритм на данном графе и проверять дополнительные условия. Я выбрал этот путь.

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

ejudgemanager.analyses → Analysis Irrelevant Elements 1

 
-1
 

Думаю основную задачу все поняли: построить n-ую строку треугольника Паскаля и вывести те элементы, которые делятся на m. Основная проблема в ограничениях... Построить 100-тысячную строку треугольника Паскаля обычным перебором требует много времени, да и сами элементы этих строк не влезают даже в 64-битный long.

Давайте по порядку:

  1. Рассмотрим треугольник Паскаля как прямоугольный треугольник, тогда i-ая строчка этого трегольника будет диагональю квадрата со стороной i+1. Далее... Если мы рассмотрим каждый элемент трегольника, то становится ясным, что его значение это количество способов добраться из точки (0,0) в эту точку, двигаясь только вправо или вниз. По комбинаторному принципу получаем: A[i][j] = C(i+j,i). Теперь рассмотрим n-ую строчку треугольника... Так они все лежат на одной диагонали, то получаем i+j=n, а C(n,i+1) = C(n,i)*(n-i-1)/(i+1). Зная, что C(n,0) = 1, мы можем за линейное время найти все элементы этой строки.

  2. Теперь возникает проблема: в каком виде вычислять эти элементы. Ведь число 100000!/(2*50000!) настолько огромное число, что даже с длинной арифметикой нам не посчитать эти элементы, тем более за 3 сек. Но так как нам не нужно их считать, а только проверить их делимость на m, то мы можем разложить число m на простые множители, затем нам нужно проверить количество каждого из тех же простых множителей в каждом элементе строки треугольника, и если для каждого из них количество в A[i][j] больше количества в m, значит A[i][j] делится на m и, соответсвенно, является нужным для нас чилом. Чтобы не считать количества этих множителей для каждого элемента, воспользуемся опять отношением C(n,i+1) = C(n,i)*(n-i-1)/(i+1). То есть изначально все количества будут равны нулю, а при нахождении нового эелемента, мы считаем те простые множители, на которые делятся (n-i-1) и m, и отнимаем те, на которые делятся (i+1) и m.

P.S. Зная что треугольник симметричный работу алгоритма можно сократить в два раза. Я так и сделал.