четверг, 23 октября 2008 г.

Google Treasure Hunt 2008

Смотреть тут...
Вот...хотелось задачки порешать.
После ProjectEuler оказалось - ни о чем. Самое сложное - ждать 10 минут проверки решения...

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

def google_test_robot():

    d={}

    d[(0,0)]=1

    def helper_(a,b):

        if a<0 or b<0:

            return 0;

        if not d.has_key((a,b)):

            d[(a,b)] = helper_(a-1,b)+helper_(a,b-1)

        return d[(a,b)]

    print helper_(41,44)



несмотря на "меморизацию" :) решение далеко не самое оптимальное как с точки зрения используемой памяти так и с точки зрения скорости. Правильнее было бы идти по диагонали...но так писать дольше.
И уж совсем правильнее было бы использовать так называемые Catalan numbers (ну это если повезло и поле квадратное)

среда, 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;

}

вторник, 19 августа 2008 г.

Задачка на вероятность.

Задачки на расчет вероятности всегда вызывали у меня определенные трудности.
Но в этот раз вроде удалось обойтись без формул.
Итак
Есть самолет, есть 100 пассажиров. В самолет заходят в порядке очереди, т.е. человек сидящий на 1м месте заходит 1м и т.д. 1й пассажир - неадекват. Заходя в самолет он садится не на свое место а на случайное. Остальные пассажири действуют так - если их место свободно они содятся на свое место. Если занято - на любое.
Вопрос - какова вероятность того что последний 100й пассажир займет свое место.

Ну понятное дело...сначала я пытался формулы выписывать.
Потом в голову пришло решение попроще...
Заметим, что у 100го пассажира не так много вариантов. Он либо займет свое место. Либо он сядет на 1е место. Другой ситуации быть не может. Более того - для любого пассажира если его место занято вероятность занять 1е место (после этого порядок восстановлен) и 100е место одинакова. Для первого пассажира также вероятность занять 1е и 100е место одинаковы. Видно что 1е место и 100е место симметричны. Вывод - 1/2.

пятница, 8 августа 2008 г.

когда приходит время договориваться...

Рассмотрим некоторую игру:

Двое играют против казино. У них есть карточки с цифрами 1 и 0. В каждом раунде они выкладывают эти карточки и получают от казино деньги. Либо же платят их казино.



1. Если оба игрока выложили 0, то они платят казино по десять рублей.
2. Если один из игроков вложил 1, а другой 0, то выложивший 1 платит сто рублей, а выложивший 0 получает сто рублей.
3. Если оба игрока выложили 1, то оба получают от казино по пятьдесят рублей.


Оба игрока очень умные и рациональные эгоисты. Они быстро просчитывают ситуацию: выгодно выкладывать 0. Поскольку в этом случае либо выигрываешь сто рублей, либо проигрываешь десять. При выложенной же единице, либо выигрываешь пятьдесят, либо проигрываешь сто. Более того, каждый знает, что соперник не дурак, проигрывать сто рублей не согласится, поэтому обязательно выложит 0.

В результате на каждом раунде игроки проигрывают по десять рублей. Хотя есть способ обоим выигрывать пятьдесят.

Этот способ можно было бы реализовать договором, однако без него каждому из игроков выгодно, чтобы соперник выложил 1, а сам игрок 0.

Ситуация не разрешима в рамках даже самого рационального эгоизма. И вывод из неё: игрокам необходимо договориться.

четверг, 7 августа 2008 г.

Задачка на переход в двоичную систему исчисления.

Просто часто попадается
есть 1000 бутылок вина. одна отравлена. есть 10 кроликов. любой из них умирает через 15-20 дней после принятия яда. яд смертелен в любых кол-ах. после 21 дня нужно определить, в какая бутылка отравлена


решение - переводим номер бутылки в двоичную систему счисления. Если n-й бит == 1 поим n-го кролика из это бутылки. На 21й день получаем число (если n-й кролик умер на n-й позиции 1 иначе 0, переводим в десятичную).

Про старушек

Из города А в город Б и из города Б в город А на рассвете одновременно вышли две старушки. В 12 часов они встретились. Потом продолжили свой путь. Одна пришла в конечный пункт в 4 часа дня, а другая — в 9 вечера. Вопрос: в каком часу рассвело в этот день?


Задача несложная...но одно но...
По легенде эта задача была задана математику В.Арнольду когда он учился в 5м классе. Соответственно решение должно быть на уровне 5го класса...

Мое решение пятикласника:

Первая старушка двигается за 4 часа прошла столько же сколько вторая от рассвета до обеда. Вторая за 9 часов прошла столько же сколько первыя от рассвета до обеда. тогда t*v1=9*v2 и t*v2=4*v1. Дальше просто но возникает квадратный корень... Не похоже на пятикласника. Проще я придумать не смог, все равно возникает квадратный корень.