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

Автор Neodym, история, 9 лет назад, По-русски

Добрый вечер!

Недавно я столкнулся со следующей проблемой: в процессе решения некой задачи мне понадобилось считать определитель достаточно специфичной матрицы:

1) Ее размер может быть до 600.

2) Все ее элементы — целые числа из множества { - 1, 0, 1}.

3) В любой ее строке и столбце не более 4 ненулевых элементов — то есть, она сильно разрежена (вообще говоря, это матрица смежности некого двудольного графа, но некоторые ребра взяты с минусом).

Притом определители надо подсчитать приблизительно у сотни таких матриц.

Я скопировал код с e-maxx.ru, и переписал все на Java, но сомневаюсь в эффективности полученного кода. Детерминант, очевидно, может быть очень большим, из-за чего приходится пользоваться BigDecimal и BigInteger с округлениями. По всем этим причинам код работает жутко медленно и нуждается в оптимизации.

 public static BigInteger determinant(final int[][] matr) {

        int accuracy = 20;

        BigDecimal EPS = BigDecimal.valueOf(0.00000000001);

        int n = matr.length;
        BigDecimal[][] a = new BigDecimal[n][n];
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < n; ++j) {
                a[i][j] = new BigDecimal(matr[i][j]);
                a[i][j].setScale(accuracy, BigDecimal.ROUND_HALF_UP);
            }

        BigDecimal det = new BigDecimal(1.0);
        det.setScale(accuracy, BigDecimal.ROUND_HALF_UP);

        for (int i = 0; i < n; ++i) {
            int k = i;
            for (int j = i + 1; j < n; ++j)
                if (a[j][i].abs().compareTo(a[k][i].abs()) > 0)
                    k = j;
            if (a[k][i].abs().compareTo(EPS) < 0) {
                det = new BigDecimal(0.0);
                det.setScale(accuracy, BigDecimal.ROUND_HALF_UP);
                break;
            }
            BigDecimal[] tmp = a[i];
            a[i] = a[k];
            a[k] = tmp;

            if (i != k)
                det = det.divide(new BigDecimal(-1), accuracy, BigDecimal.ROUND_HALF_UP);
            det = det.multiply(a[i][i]);
            for (int j = i + 1; j < n; ++j)
                a[i][j] = a[i][j].divide(a[i][i], accuracy, BigDecimal.ROUND_HALF_UP);
            for (int j = 0; j < n; ++j)
                if (j != i && a[j][i].abs().compareTo(EPS) > 0)
                    for (int kk = i + 1; kk < n; ++kk) {
                        BigDecimal aikji = new BigDecimal(1.0);
                        aikji.setScale(accuracy, BigDecimal.ROUND_HALF_UP);
                        aikji = aikji.multiply(a[i][kk]);
                        aikji = aikji.multiply(a[j][i]);
                        aikji = aikji.multiply(new BigDecimal(-1));
                        a[j][kk] = a[j][kk].add(aikji);
                    }
        }

        det = det.abs();
        det = det.add(new BigDecimal(0.00001));
        return det.abs().toBigInteger();

    }

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

UPD. Из всех предложенных вариантов подошел следующий: так как для матрицы размера N × N ответ не превосходит 2N, то можно привести матрицу к верхнетреугольному виду, выполняя все вычисления по модулю prime, где prime — простое число битовой длины не менее N — найти его помогут встроенные функции BigInteger. Скорость по сравнению с реализацией на BigDecimal возросла чуть ли не в десяток раз.

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

Код, если кому-то будет интересен:

   public static BigInteger determinant(final int[][] matr) {

        int n = matr.length;
        BigInteger[][] a = new BigInteger[n][n];
        for (int i = 0; i < n; ++i) {
            for (int j = 0;j  < n; ++j) {
                a[i][j] = BigInteger.valueOf(matr[i][j]);
            }
        }

        BigInteger prime = BigInteger.probablePrime(n + 4, new Random());

        BigInteger det = BigInteger.ONE;

        for (int row = 0; row < n; ++row) {
            int currentRow = row;
            while (currentRow < n && a[currentRow][row].equals(BigInteger.ZERO)) {
                ++currentRow;
            }
            if (currentRow == n) {
                return BigInteger.ZERO;
            }

            if (currentRow != row) {
                det = det.negate();
                BigInteger[] tmp = a[currentRow];
                a[currentRow] = a[row];
                a[row] = tmp;
            }

            BigInteger inverse = a[row][row].modInverse(prime);

            for (currentRow = row + 1; currentRow < n; ++currentRow) {
                if (a[currentRow][row].equals(BigInteger.ZERO)) {
                    continue;
                }
                BigInteger coefficient = a[currentRow][row].multiply(inverse).remainder(prime);
                for (int column = row; column < n; ++column) {
                    a[currentRow][column] = a[currentRow][column].subtract(a[row][column].multiply(coefficient).remainder(prime)).remainder(prime);
                }
            }

        }

        for (int i = 0; i < n; ++i) {
            det = det.multiply(a[i][i]).remainder(prime);
        }
        det = det.add(prime);
        det = det.remainder(prime);
        if (det.multiply(BigInteger.valueOf(2)).compareTo(prime) > 0) {
            det = prime.subtract(det).remainder(prime);
        }
        return det;
    }

Полный текст и комментарии »

  • Проголосовать: нравится
  • +21
  • Проголосовать: не нравится

Автор Neodym, 10 лет назад, По-русски

Всем доброго времени суток!

Недавно я заинтересовался следующей проблемой: пусть у нас есть плоскость, на которой проведено n прямых общего положения — то есть, никакие 2 не параллельны, никакие 3 не пересекаются в одной точке. Ясно, что эти прямые разбивают плоскость на некоторые части — притом, площадь некоторых конечна, а некоторых — бесконечна. Подсчитаем число частей, которые являются треугольниками. Поставим теперь "экстремальную" задачу — обозначим за f(n) минимальное число треугольников, которое может получиться, а за g(n) — максимальное.

Оказывается, что f(n) = n - 2 для любого n ≥ 3. Эта теорема была недоказанной на протяжении больше сотни лет, пока ее не доказали достаточно хитроумным способом, описанным, например, в статье "Треугольники и катастрофы".

К сожалению, найти какие-то оценки на величину g(n) в Интернете у меня не получилось. Можно, однако, получить тривиальную квадратичную оценку с помощью формулы Эйлера: рассмотрим нашу картинку как планарный граф — вершинами будут точки пересечения прямых, ребрами — те отрезки между вершинами, которые более ничем не пересекаются. Обозначим количество вершин за V, ребер — за E, граней (частей с конечной площадью) за F. На приведенном рисунке V = 10, E = 15, F = 6. Вообще, нетрудно перебрать все варианты и обнаружить, что g(5) = 5.

Как известно из формулы Эйлера, V - E + F = 1(мы не учитываем "внешнюю" грань). V и E по несложным соображениям можно высчитать как и n(n - 2), отсюда мы видим, что количество частей с конечной площадью независимо от разбиения равняется — собственно, это и дает тривиальнейшую оценку на количество треугольников как что-то порядка .

В связи с этим возник вопрос — а достигается вообще ли эта оценка? Чтобы показать определенную нетривиальность задачи подмечу, что g(9) ≥ 14:

Update челлендж закрыт, поскольку я ошибочно предполагал, что g(n) имеет порядок не больший, чем nlog(n) и господа Endagorion и Ripatti привели квадратичные примеры. Собственно, с задачей я встретился, пытаясь улучшить константу вот здесь — если есть любители математики, которые захотят порешать:)

Полный текст и комментарии »

  • Проголосовать: нравится
  • +67
  • Проголосовать: не нравится

Автор Neodym, 10 лет назад, По-русски

Добрый день! Недавно я столкнулся с интересной математической задачей: "Докажите, что если в куб можно поместить три шара радиуса 1, то можно поместить и четыре шара радиуса 1".

Чтобы решить задачу, ее нужно немного переформулировать. Рассмотрим внутренний куб, все стороны которого отстоят от внешнего на 1. В этом внутреннем кубе лежат центры шаров, притом расстояние между любыми двумя не меньше 2. Введем функцию f(n) — минимальный размер куба, в который можно поместить n точек так, что расстояние между любыми двумя не меньше 2. По сути, в задаче требуется доказать, что f(3) = f(4) (и на самом деле они обе равны , как показывает несложный анализ).

Равенство достигается на очень простой конфигурации — надо поместить четыре точки в вершины куба через одну. И тут мне стало интересно — а как посчитать f(5), и как выглядит оптимальная конфигурация в таком случае?

Математические рассуждения, прокатывавшие для 3 и 4, тут уже, к сожалению, не работают. Я решил воспользоваться компьютером, и реализовал алгоритм, работающий на следующих рассуждениях: Во первых, зафиксируем размер куба как 1, и будем решать такую задачу: для каждого расположения точек в кубе мы запишем минимальное расстояние между точками, и это минимальное расстояние должно быть максимальным. Ясно, что .

Заметим, что если взять любое расположение точек в кубе, и сдвинуть каждую точку не более, чем на x, то любое расстояние изменится не более, чем на 2x. Разобьем наш куб на 3 * 3 * 3 маленьких кубиков. Будем считать, что все точки самой удачной конфигурации лежат в этих центрах этих маленьких кубов — и переберем всевозможные комбинации. После этого можно считать, что точки из самой удачной конфигурации лежат на расстоянии не более (это половина диагонали куба со стороной ) от полученных точек, поэтому вокруг каждой мы можем нарисовать куб, и считать, что точки из самой удачной конфигурации лежат в этом кубе. На следующей итерации мы делим получившиеся кубики на меньшие (3 * 3 * 3), и начинаем перебирать (считаем, что первая точка теперь находится в первом кубе, вторая во втором, и т.д.). В конце каждой итерации мы получаем оптимальную конфигурацию с определенной точностью, и вновь повторяем процесс. Минусы такого подхода очевидны: на любой итерации перебирается 275 случаев, что приблизительно равно 107. Тем не менее, где-то за 20 минут компьютер с хорошей точностью посчитал f(5) как 1.7889, что практически равно . Соответственно, самая выгодная расстановка 5 точек в кубе: (0, 0, 0), (1, 0.5, 0), (0.5, 0, 1), (0, 1, 0.5), (1, 1, 1) (в относительных координатах). Если f(6) еще можно будет посчитать за адекватное время, то нахождение f(7) выглядит уже достаточно сложной задачей.

Таким образом, хотел бы послушать еще какие-нибудь полезные идеи по поводу алгоритма:)

P.S Оказалось, что результат для f(6) довольно тривиален — это 2, равенство достигается, когда мы просто суем точки в вершины куба. Отсюда следует, что f(7) = f(8) = 2. В общем, теперь жду каких-то интересных комментариев по поводу того, как посчитать f(9) :)

Полный текст и комментарии »

  • Проголосовать: нравится
  • +23
  • Проголосовать: не нравится

Автор Neodym, 10 лет назад, По-русски

Доброго времени суток! 20 августа в 19:30 по московскому времени состоится 262 (Div. 2) раунд. Участники первого дивизиона могут принимать участие вне конкурса.

Задачи подготовили 2 автора — Василевский Виктор(vitux) и Семченков Алексей (я). Мы приносим благодарность Геральду Агапову (Gerald) за помощь в подготовке раунда, Михаилу Мирзаянову (MikeMirzayanov) за удобные платформы Codeforces и Polygon, и Марии Беловой (Delinur) за перевод условий на английский язык.

В задачах раунда вам придется помогать разным хорошим людям. Мы надеемся, что задачи покажутся интересными участникам обоих дивизионов:)

gl & hf!

UPD: Будет использована стандартная разбалловка.

UPD2: Также мы благодарим Виталия Аксенова(Aksenov239) за правку русских условий задач.

UPD3: Приносим поздравления пятерке победителей:

1) buptcjj

2) 6wr1

3) ladpro98

4) shaojj

5) linjek

Также приносим поздравления jellygendut, который занял 20е место и решил задачу Е — ее решило всего 3 человека.

Никто не решил все задачи, но каждая задача была решена хоть кем-то.

Полный текст и комментарии »

  • Проголосовать: нравится
  • +159
  • Проголосовать: не нравится

Автор Neodym, 12 лет назад, По-русски

Подскажите пожалуйста, как подключать таймеры в языке С++(Чтобы можно было определять, сколько времени прошло с начала работы программы)

UPD: Понял и осознал. Спасибо всем, кто ответил!

Полный текст и комментарии »

  • Проголосовать: нравится
  • 0
  • Проголосовать: не нравится

Автор Neodym, 12 лет назад, По-русски

Подскажите пожалуйста, как обьявлять массив из строк в С++, чтобы к строкам можно было обращаться образом вроде A[i]?

Следующие способы не срабатывают:

string A[N];

или

vector< string > a(N);

UPD: Понял и осознал. Спасибо всем, кто ответил.

PS: Спасибо за минусы:D

Полный текст и комментарии »

  • Проголосовать: нравится
  • -3
  • Проголосовать: не нравится