10. Динамично оптимиране - задача за раницата [8.2.1] - 23.12.2003 от 14:40 до 17:00 часа

    Класическа задача за раницата:
    Дадена е раница с вместимост M килограма и N предмета, всеки от които се характеризира с две числа - тегло mi и стойност ci. Да се избере такова множество от предмети, чиято сумарна стойност е максимална, а сумата от теглата не надвишава M.

    Дефинираме рекурентна целева функция:

F(0) = 0;  F(i) = max { cj + F(i-mj),  j = 1, 2, ..., Nmj <= i }, i > 0

    Методът на динамичното оптимиране изисква последователно пресмятане на стойностите на F(i), като за това пресмятане се използват вече пресметнатите стойности за по-малки i.

// knapsack.c
#include <string.h>
#include <stdio.h>

#define MAXN 30    /* max брой предмети */
#define MAXM 1000  /* max вместимост на раницата */

char set[MAXM][MAXN];
/* ако set[i][j]==1, то на стъпка i (вместимост i) в раницата е предмет j */

unsigned Fn[MAXM]; /* стойност на целевата функция */

/* ПРЕДМЕТИ */
const unsigned m[MAXN] = {0, 30, 15, 50, 10, 20, 40, 5, 65}; /* тегло */
const unsigned c[MAXN] = {0,  5,  3,  9,  1,  2,  7, 1, 12}; /* стойност */

const unsigned M = 70; /* вместимост на раницата */
const unsigned N = 8;  /* брой предмети */

void calculate(void)
{ unsigned maxValue, maxIndex, i, j;
  memset(set,0,sizeof(set)); /* set[i][j] = 0 */

  for (i=1; i<=M; i++)
  { maxValue = maxIndex = 0;
    for (j=1; j<=N; j++)              /* опит с предмета с номер j */
     if (m[j]<=i && !set[i-m[j]][j])
      if (c[j] + Fn[i-m[j]] > maxValue)
    { maxValue = c[j] + Fn[i-m[j]];
      maxIndex = j;
    }
    if (maxIndex>0)                  /* има успешен опит */
    { Fn[i] = maxValue;
      memcpy(set[i],set[i-m[maxIndex]], N);
      set[i][maxIndex] = 1;
    }
    if (Fn[i] < Fn[i-1])
    { Fn[i] = Fn[i-1];
      memcpy(set[i],set[i-1], N);
    }
  }
  printf("No:");
  for (i=1; i<=N; i++)
     if (set[M][i]) printf("%5u", i);
  printf("\n Max value: %u", Fn[M]);
}

int main()
{ calculate();
  return 0;
}
    Да разгледаме примера:
N = 8;
index      0  1  2  3  4  5  6  7  8
m[MAXN] = {0, 3, 7, 6, 1, 2, 4, 5, 5};
c[MAXN] = {0, 5, 3, 9, 1, 1, 2, 5, 2}
M = 7;

Fn[0] = 0;
Fn[1] = max { c[4]+Fn[0] } = 1 /4/
Fn[2] = max { c[5]+Fn[0] } = 1 /5/
Fn[3] = max { c[1]+Fn[0], c[4]+Fn[2], c[5]+Fn[1] } = max{5,3,2} = 5 /1/
Fn[4] = max { c[1]+Fn[1], c[4]+Fn[3], c[6]+Fn[0] } = max{6,6,2} = 6 /1,4/
Fn[5] = max { c[1]+Fn[2], c[5]+Fn[3], c[6]+Fn[1], c[7]+Fn[0], c[8]+Fn[0] } = max{6,6,3,5,2} = 6 /1,5/
Fn[6] = max { c[3]+Fn[0], c[4]+Fn[5], c[5]+Fn[4], c[6]+Fn[2], c[7]+Fn[2], c[8]+Fn[1] } = max{9+0,1+5,1+6,2+1,5+1,2+1} = 9 /3/
Fn[7] = max { c[2]+Fn[0], c[3]+Fn[1], c[4]+Fn[6], c[6]+Fn[5], c[7]+Fn[2], c[8]+Fn[5] } = {5+0,9+1,1+9,2+6,5+1,2+6} = 10 /3,4/

Задача, дадена на Шести междууниверситетски турнир по програмиране - Шумен, 10 май 2003 г.

Задача F.  Зайче в беда

    Веднъж малкото бяло зайче, гонено от един ловец попаднало в горния ляв ъгъл на лабиринт, които имал форма на квадратна дъска N x N. В него чакал големия лош вълк, които предварително изкопал дупки, където зайчето да падне, а той да го хване по-лесно. В последния момент зайчето с ужас разбрало, че може да се движи само в посока надолу и надясно, и че изхода от лабиринта е чак в долния десен ъгъл на дъската.
    Зайчето трябвало да разбере каква е вероятността да излезе от лабиринта без да падне в някоя дупка. За целта трябвало да изчисли броя пътища от входа до изхода на лабиринта, като успяло да се снабди с картата на този лабиринт. Местата на дупките на картата са означени с 0, а проходимите места с 1. Напишете програма F.EXE, която пресмята търсения брой пътища.
    Данните се четат от стандартния вход, където на първия ред е записано цялото число К - броя на тестовите примери, а за всеки тестов пример се дава числото N <= 40;  следват N реда с по  N числа, като на всяка клетка съответства по едно число 1 или 0 (проходима клетка или дупка).
    На стандартния изход се извеждат К числа - по едно за всеки тестов пример, показващи броя на пътищата за съответния тестов пример. Всяко едно от числата се извежда на отделен ред.

Пример.
 

Вход:
1
4   
1 1 1 1 
1 1 0 1
1 0 1 1
1 1 1 1
Изход:
2

Допълнителна литература:
[3] Лендерт Амерал, Алгоритми и структури от данни в С++, ИК "Софтех", София,  2001, стр. 340
[4] Емил Келеведжиев, Динамично опримиране, Мусала Софт и Анубис, София 2001.
[6] Робърт Седжуик, Алгоритми на С. Том 1, ИК "Софтех", София, 2002, стр. 229.