SRM 397

Apr 13, 2008 04:13

↑1481

Я вошел в 1/5 лучших программистов мира (процентиль 82.7902) и чуть-чуть не дотянул до желтого цвета и призового 3-го места в комнате (стал 4-м).



250
Условие

В задаче требовалось найти минимальное число шагов, сортирующих перестановку {1, ..., n}, где за каждый шаг можно выбрать k рядом стоящих чисел и обратить их порядок. Причем n был до неприличия маленьким - не больше 8.

Нетрудно заметить, что 8! = 40320, так что даже полный пошаговый перебор всех возможных перестановок едва ли займет много времени. Что я и сделал. Максимальное время на тесте {{8,7,6,5,4,3,2,1}, 2} составило 260 мс.

#include
#include
#include
#include

using namespace std;

typedef vector vi;

class SortingGame
{
public:
    int fewestMoves(vector board, int k)
    {
        vi so = board;
        sort(so.begin(), so.end());

if (board == so) return 0;

int n = board.size();
        set< vi > all;
        vector< set< vi > > v;
        set< vi > e;
        v.push_back(e);
        v[0].insert(board);
        int c = 0;
        bool end = false;

for (;;) {
            end = true;
            v.push_back(e); c++;
            for (int i = 0; i <= n - k; i++) {
                for (set::iterator i_s = v[c - 1].begin(); i_s != v[c - 1].end(); i_s++) {
                    vi b = *i_s;
                    int j, l;
                    for (j = i, l = i + k - 1; j < l; j++, l--) {
                        int t = b[j];
                        b[j] = b[l];
                        b[l] = t;
                    }

if (all.find(b) == all.end()) {
                        end = false;
                        v[c].insert(b);
                        all.insert(b);
                        if(b == so) return c;
                    }
                }
            }
            if (end) return -1;
        }
    }
};

500
Условие

В этой задаче нужно всего-то найти сумму 1k + 2k + ... + nk по модулю 1000000007. Но вот беда, k может быть до 50, а n аж до 109.

Взглянув на эту задачу, я сразу подумал: «Боян!». Действительно, подобная задача есть на Тимусе и даже в Демидовиче (кажется). Задачей этой интересовался еще Бернулли; он решил ее, доказав, что сумма k-ых степеней чисел от 1 до n выражается через полином k+1-й степени от n. Основная проблема состоит в вычислении этих коэффициентов, но Бернулли справился и с этим, введя числа Бернулли (логично, не правда ли?). Сейчас применений у этих чисел достаточно много, поэтому формула для суммы степеней называется формулой Faulhaber (я постеснялся предположить транскрипцию). Вот она:



Писать длинную арифметику не хотелось. После недолгих экспериментов оказалось, что C# на TopCoder старый, и там System.Numeric.BigInteger нет. А на Java я начал писать слишком поздно, причем, как показало дорешивание, и большее количество времени не помогло бы (даже несмотря на то, что числа Бернулли я захардкодил, воспользовавшись Maple. А вот что такое с размерностью 100x50 захардкодил pawa в этой задаче, мне до сих пор любопытно знать). Жутко интересно, какой такой логичностью объясняется то, что у этого класса нет обычных арифметических операторов, конструктора из int и других вещей, настолько очевидных, что ради них даже как-то странно заглядывать в мануал?

Поэтому я, наконец, решился прочитать решение Jedi_Knight, которое он мне описал :)

(Далее простите за формулы c texify.com, я так и не разобрался, как заставить wordpress.com рендерить матрицы)

Хитрый Джедай заметил, что вектор (1, n, n2, n3, ...) отображается оператором с матрицей треугольника Паскаля



в вектор (1, n+1, (n+1)2, (n+1)3, ...). Очевидно, что достаточно применить этот оператор N−1 раз к вектору (1, 1, 1, 1, ...) (либо N раз к (1, 0, 0, 0, ...)), чтобы получить вектор (1, N, N2, N3, ...). А теперь заметим, что
. Это значит, что мы можем добавить в вектор новую компоненту, равную
и слегка видоизменить матрицу оператора. Тогда его действие будет выглядеть так:



Интересно, догадался бы до этого наш линалист Борат? :)

Применить полученный оператор к вектору (1, 0, 0, 0, ...) n раз (скажем, миллиард) достаточно просто. Для этого возведем матрицу оператора в n-ю степень (напомню, умножение матриц вполне себе ассоциативно, хотя и некоммутативно), а это можно сделать простым матричным возведением в степень, когда считаются только A2k, а An получается перемножением соответствующих матриц. Так-то!

#include
#include

using namespace std;

class SumOfPowers
{
    const static int MAGIC = 1000000007;
    const static int MDIM = 53;
    const static int MBSIZE = sizeof(int) * MDIM * MDIM;

public:
    void matrix_multiply(int a[MDIM][MDIM], int b[MDIM][MDIM], int dim)
    {
        int c[MDIM][MDIM];
        for (int i = 0; i < dim; i++) {
            for (int j = 0; j < dim; j++) {
                long long res = 0;
                for (int k = 0; k < dim; k++) {
                    res += ((long long)a[i][k] * (long long)b[k][j]) % MAGIC;
                }
                c[i][j] = res % MAGIC;
            }
        }
        memcpy(a, c, MBSIZE);
    }

int value(int n, int k)
    {
        // Треугольник Паскаля
        int a[MDIM][MDIM]; memset(a, 0, MBSIZE);
        a[0][0] = 1;
        for (int i = 0; i <= k; i++) {
            a[i][0] = a[i][i] = 1;
        }
        for (int i = 0; i <= k; i++) {
            for (int j = 1; j < i; j++) {
                a[i][j] = (long long)(a[i-1][j-1] + a[i-1][j]) % MAGIC;
            }
        }

// Добавляем строчку
        for (int i = 0; i < k; i++) {
            a[k+1][i] = 0;
        }
        a[k+1][k] = a[k+1][k+1] = 1;

// Возводим в n-ю степень
        int p[32][MDIM][MDIM];
        memcpy(p[0], a, MBSIZE);
        for (int i = 1; i < log2(n) + 1; i++) {
            matrix_multiply(a, a, k + 2);
            memcpy(p[i], a, MBSIZE);
        }

int res[MDIM][MDIM]; memset(res, 0, MBSIZE);
        for (int i = 0; i < k+2; i++) {
            res[i][i] = 1;
        }
        for (int i = 0; i < log2(n) + 1; i++) {
            if (n & (1 << i)) {
                matrix_multiply(res, p[i], k + 2);
            }
        }

// Умножаем на вектор-столбец [1,1,1,...,1,0]
        long long result = 0;
        for (int i = 0; i <= k; i++) {
            result += res[k+1][i];
        }

return result % MAGIC;
    }
};

К слову, в чате Арены один игрок заявил, что уже отсылал эту задачу организаторам, но те развернули ее с формулировкой «слишком математическая» :). Так что TopCoder растет, и вскоре и впрямь понадобится доказывать теорему Римана в частном случае 250-й (давайте все дружно подколем сами-знаете-кого :)).

Вы могли заметить (а могли и не заметить), что отныне я придерживаюсь в своем коде с Си-синтаксисом стандартов кодирования PEAR. Это круто.

Аддон: Тут выяснилось, что BigInteger удален из .NET 3.5, предположительно из-за его низкого быстродействия. Это ужасно.

задачи, программирование, олимпиады

Previous post Next post
Up