среда, 24 сентября 2008 г.

Project Euler 201 - не выходит

Почему вообще заинтересовала эта проблема? Во первый мне показалось что итеративный подход намного красивее и понятнее функционального.
А во вторых...написав в принципе правильный алгоритм я получил неправильный результат. И довольно долго не мог понять в чем собственно дело.
Задача:
Пусть у нас есть массив S={1*1,2*2,...100*100}
Найти сумму чисел которые являются уникальной ссуммой 50-элементного подмассыва S.
Более подробно на сайте...

Код (неправильный)

    char* total = new char[300000*50];

    memset(total,0,300000*50);

 

    for(int i=1;i<=100;++i){

        for(int j=299999;j>0;--j)

            for(int p=0;p<49;++p)

                if(total[j*50+p])

                    if(total[j*50+p])

                        total[(j+i*i)*50 + p + 1] += total[j*50+p];

        total[i*i*50]=1;

    }

 

    LONGLONG res = 0;

    for(int j=49;j<300000*50;j+=50)

        if(total[j]==1)

            res+=(j-49)/50;

    delete total;

    return res;

четверг, 4 сентября 2008 г.

Самая длинная общая подпоследовательность

Опишу-ка я некоторые алгоритмы которые "я всегда знал но забыл".
Начнем с длины самой длинной общей подпоследовательности.
Пусть у нас есть два массива A=(a1...ai) и B=(b1...bj)
Тогда LCS(A,B)=:
1. 0 если i==0 или j==0;
2. LCS((a1...ai-1) ,(b1...bj-1))+1, если ai==bj
3. MAX(LCS((a1...ai-1) ,(b1...bj)),LCS((a1...ai) ,(b1...bj-1))), если ai!=bj

Это достаточно очевидные утверждения, и по ним уже можно составить алгоритм.
Алгоритм просто напрашивается:

int lcs(int* a,int N1,int* b,int N2)

{

    if(N1==0 && N2==0)

        return 0;

    if(a[N1-1) == b[N2-1])

        return 1+lcs(a,N1-1,b,N2-1);

    return max(lcs(a,N1,b,N2-1),lcs(a,N1-1,b,N2))

}



красотища...если бы не одно но. очень медленно.
И в целом почти сразу понятно почему. Мы не храним промежуточные вычисления а каждый раз делаем их заново.
Класический пример такой проблемы - вычесление чилес Фибоначчи по рекурсии:

int F(int N)

{

    return (N<2)?1:F(N-2)+F(N-1);

}


Улучшить это можно, сохраняя промежуточные результаты вычислений. Этот прием называется "меморизация". Т.е. вообще-то он называется "Memoization", но надо же как-то по русски сказать.

В данном случае для меморизации нам понадобится массив размером [i*j].
Но тут возникает интересный вопрос - а зачем нам тогда рекурсия здалась?
И возникает примерно такой код (пробывать не советую, не тестировал, могу ошибиться с индексами)

int lcs_array(int* a,int N1,int* b,int N2)

{

    int* c = new int[N1*N2];

    memset(c,0,N1*N2*sizeof(int));

    for(int i=0;i<N1;++i)

        for(int j=0;j<N2;++j)

            c[i*N2+j]=(a[i]==b[j])?c[(i-1)*N2+j-1]+1:max(c[i*N2+j-1],c[(i-1)*N2+j]);

    int res = c[N1*N2-1];

    delete[] c;

    return res;

}

среда, 3 сентября 2008 г.

Google Code Gem - round 1A

Первая задача первого раунда была повеселее...

Problem

You are given two vectors v1=(x1,x2,...,xn) and v2=(y1,y2,...,yn). The scalar product of these vectors is a single number, calculated as x1y1+x2y2+...+xnyn.

Suppose you are allowed to permute the coordinates of each vector as you wish. Choose two permutations such that the scalar product of your two new vectors is the smallest possible, and output that minimum scalar product.


Как решал я...
Ну во первых перебор это конечно хорошо для малых n...но поскольку в большем тестовом задании n==800 а 800! это "много" от перебора можно отказаться сразу.

Сначала делаем матрицу n*n, такую что m[i][j]=v1[i]*v2[j];
Обычное скалярное произведение это Cум(m[i][i]). У нас же эта будет такая сумма что i и j встречаются только один раз.

Рассмотрим следующую идею.
Возьмем какое-то скалярное произведение и будем его улучшать до тех пор пока улучшение возможно.
Как улучшать - ищем такие m[i][j] и m[l][k] что что m[i][j]+m[l][k]>m[i][k]+m[l][j]. Как только нашли меняем m[i][j] на m[i][k] и m[l][k] на m[l][j]. Если не смогли найти - ура.

Основной код, очищенный от ввода/вывода:

        vector<pair<int,int>> product;

        for(int i=0;i<a1.size();++i)

            product.push_back(std::make_pair(i,i));

 

        while(true)

        {

            bool improved = false;

            for(vector<pair<int,int>>::iterator v=product.begin();!improved && v!=product.end();++v)

            {

                for(vector<pair<int,int>>::iterator v1=v+1;!improved && v1!=product.end();++v1)

                {

                    int i=v->first;

                    int j=v->second;

                    int l=v1->first;

                    int k=v1->second;

                    if(m[i*a1.size()+j]+m[l*a1.size()+k]>m[i*a1.size()+k]+m[l*a1.size()+j])

                    {

                        v->second = k;

                        v1->second = j;

                        improved = true;

                    }

                }

            }

            if(!improved)

                break;

        }



Остально доказать что такой метод действительно приведет нас к лучшему результату а не просто к неплохому результату...Такие вещи я доказываю очень плохо..но кажется по индукции и это доказуемо.

Google Code Gem - Qualification

Так уж получилось что я его пропустил. Поэтому просто порешаю для себя.
Кволификация простая.
Я ее переформулирую чтобы было поинтереснее...
Пусть у нас есть последовательность состоящая из нолей и других различных N чисел.
Например - 0,0,1,0,2,2,0,0,1,3,0,0,0,0,3,0,2
Пусть у нас есть N тригеров. В начальный момент времени какой-то тригер i включен, остальные выключены. Мы сканируем последовательность. Когда встречается номер текущего включенного тригера мы должны этот триггер выключить и включить какой-то другой.
Например - пусть у нас включен в начале 1й триггер, тогда на 3м шаге мы должны выключить 1й и включить какой-то другой.
Задача - минимизировать количество переключений триггеров.


Решение которое пришло мне в голову достаточно простое. Пришлось только повозиться с доказательством его правильности). Решение в виде "жадного" алгоритма. Мы сканируем последовательность отмечая места в который i-й триггер нужно выключить. Когда все триггеры отмечены - последний отмеченный и будет пырвым триггером который мы должны выбрать для включения. Затем помечаем его как включенный и повторяем операцию для оставшейся части последовательность. Если последовательность закончилась - берем 1й попавшийся.

std::vector<int> google_code_gem_intro(std::vector<int>& in,int length)

{

    std::vector<int> res;

    std::vector<int> b;

    for(int i=0;i<length;++i)

        b.push_back(0);

 

    int total=length;

    for(std::vector<int>::iterator tV=in.begin();tV!=in.end();++tV)

    {

        if(!*tV)

            continue;

        if(    b[*tV]==0)

        {

            --total;

            if(!total)

            {

                res.push_back(*tV);

                total = 0;

                for(int i=0;i<length;++i)

                    b[i]=0;

                total=length-1;

            }

            b[*tV]=1;

        }

    }

    if(total)

    {

        for(int i=0;i<length;++i)

        {

            if(!b[i])

            {

                res.push_back(i);

                break;

            }

        }

    }

    return res;

}