4. Динамично оптимиране

Числа на Фибоначи [8.3.1]

* Числа на Фибоначи: F(0) = F(1) = 1 и F(i) = F(i -1) + F(i - 2) за i > 1.

1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
* Класически рекурсивен (неефективен) алгоритъм:
unsigned long fib(unsigned n) 
{ if (n < 2) return 1;
return fib(n - 1) + fib(n - 2);
}
Експоненциална сложност - многократно извикване на функцията с един и същи аргумент!

* Динамично програмиране - със запомняне на вече пресметнатите стойности:

#include <stdio.h> 
#define MAX 256

unsigned n = 10;
unsigned long m[MAX + 1];

unsigned long fibMemo(unsigned n)
{ if (n < 2) m[n] = 1;
else if (0 == m[n]) m[n] = fibMemo(n - 1)+ fibMemo(n - 2);
return m[n];
}
int main()
{ memset(m, 0, MAX*sizeof(*m));
scanf("%u", &n);
printf("\n%u-тото число на Фибоначи e", %lu", n, fibMemo(n));
return 0;
}
Линейна сложност - всяка стойност се пресмята само веднаж.

Биномни коефициенти [8.3.2]

Нека C(n, k) е биномният коефициент "n над k" или комбинации без повторения от n елемента k-ти клас. 
Общата формула е: C(n, k) = n!/((n - k)! k!), а от триъгълника на Паскал имаме и рекурентна формула:

C(n, k) = 1,  за k = 0 или k = n; C(n, k) = C(n - 1, k - 1) + C(n - 1, k),  за 0 < k < n; C(n, k) = 0,  за k > n.

* Рекурсивен неефективен алгоритъм:

unsigned long binom(unsigned n, unsigned k)
{ if (k > n) return 0;
if (k == 0 || k == n) return 1;
return binom(n - 1, k - 1) + binom(n - 1, k);
}

* Динамично програмиране - със запомняне на вече пресметнатите стойности. Не е необходимо да се пази цялата таблица C(n, k), а само един ред от таблицата - предишния.

#define MAX 200
unsigned long m[MAX];
unsigned long binomDynam(unsigned n, unsigned k)
{ unsigned i, j;
for (i = 0; i <= n; i++)
{ m[i] = 1;
if (i > 1)
{ if (k < i - 1) j = k; else j = i - 1;
for (; j >= 1; j--) m[j] += m[j - 1];
}
}
return m[k];
}
Пример: n = 5, k = 3; C(5,3) = 10.
i k<i-1
начално j m 0  1  2  3  4
0 -
-   1  1  1  1  1
1 -
-   1  1  1  1  1
2 3<2-1
1
  1  2  1  1  1
3 3<3-1
2   1  3  3  1  1
4 3<4-1
3   1  4  6  4  1 
5
3<5-1
3
  1  5 10 10  5
Този алгоритъм има сложност O(nk).

Задача 4a. Числа на Фибоначи

Да се напише програма (4a.cpp или 4a.c) за пресмятане на n-тото число на Фибоначи със запомняне на вече пресметнати стойности и използване на формулата:

F(n) = F2(n/2) + F2(n/2 - 1), за n четно; F(n) = F(n - 1) + F(n - 2), за n нечетно.
Да се намери най-голямото n, за което написаната програма пресмята вярно F(n). (Логаритмична сложност.)

Задачи за симулации

Задача 4b. Едномерен дискретен свят

На една отсечка (едномерен свят) с дължина N едновременно се раждат M обитатели на точки от отсечката с цели координати. Всеки току-що роден обитател има две възможности: да тръгне наляво (отрицателна посока) или да тръгне надясно (положителна посока). Скоростта на движение на всички обитатели е еднаква и е равна на единица разстояние за единица време. По време на движението на един обитател могат да се случат две неща: или той да се сблъска с друг обитател или да достигне до края на отсечката. В първия случай двамата обитатели се поздравляват и тръгват в обратни посоки, а във втория случай обитателят пада от отсечката и престава да съществува. Да се напише програма (4b.cpp или 4b.c), която намира в кой момент от време и кой последен обитател на едномерния свят ще престане да съществува.
Вход

На стандартния вход се четат няколко примри. Всеки пример започва с две цели положителни числа N и M, след което следват M реда с данни за обитателите - посока на движение, място на раждане и име. Посоките на движение са  p - положителна и n - отрицателна. различните обитатели се раждат на различни места.  Имената са низове до 10 букви. Входът завършва с 0 0.
Изход
На стандартния изход се отпечатват времето на падане на последния обитател и неговото име.

Пример за вход
10 4
p 1 Helga
n 3 Joanna
p 5 Venus
n 7 Clever
0 0
Изход - решение на примерния вход
9 Venus

Задача, дадена на регионалното състезание на ACM в Букурещ през 2005 година.