Динамическое программирование / комбинации без повторений и порядок не важны

#java #algorithm #combinations #dynamic-programming #combinatorics

#java #алгоритм #комбинации #динамическое программирование #комбинаторика

Вопрос:

Допустим, у нас есть некоторая железная дорога длиной L. Нам нужно разместить K станций технического обслуживания между первым и последним километром этой железной дороги. Станции технического обслуживания на 0 и L км уже построены бесплатно. Даны цена строительства станции технического обслуживания на каждом километре этой железной дороги и уравнение для расчета цены обслуживания для заданной длины железной дороги.

Например, у нас есть 4 км железной дороги, и мы получаем список цен на строительство одной станции на n-м километре железной дороги :

 5 22 13
  

также у нас есть уравнение для обслуживания между станциями — PRICE(len) = 2 * len*len 3*len.

Итак, нам нужно построить 1 станцию, мы можем сделать это на:

1-й км — это будет стоить 5 для построения и ЦЕНА (1) = 5 (обслуживание от 0 до 1 км) Цена (3) = 27 (обслуживание от 1 до 4 км) => 37

2-й км — это будет стоить 22 для построения и ЦЕНА (2) = 14 (обслуживание от 0 до 2 км) Цена (2) = 14 (обслуживание от 2 до 4 км) => 50

3-й км — это будет стоить 13 для построения и ЦЕНА (3) = 27 (обслуживание от 0 до 3 км) Цена (1) = 5 (обслуживание от 3 до 4 км) => 69

Лучший вариант — построить 1 станцию на первом километре.

Что, если нам нужно построить 2 станции?

  |S|_|_|_|F|

 |S|1|2|_|F|  5 22 PRICE(1) PRICE(1) PRICE(2) = 5   22   5   5   14 = 51 
 |S|1|_|3|F|  5 13 PRICE(1) PRICE(2) PRICE(1) = 5   13   5   14   5 = 42
 |S|_|2|3|F|  22 13 PRICE(2) PRICE(1) PRICE(1) = 22   13   14   5   5 = 59
  

Итак, лучший способ — разместить две станции на первом и третьем км.

Моя задача — найти минимальную цену за заданную длину, количество станций для сборки, цены на строительство станции на конкретном километре и уравнение.

Моя идея состояла в том, чтобы рассчитать таблицу len, чтобы узнать стоимость поддержания определенной длины. Для данного примера это таблица:

  ------ ------ ------ ------ 
| idx0 | idx1 | idx2 | idx3 |
 ------ ------ ------ ------ 
|    0 |    5 |   14 |   27 |
 ------ ------ ------ ------ 
  

Затем я вычисляю таблицу со стоимостью строительства любых двух станций на определенных километрах:

 ╔══════╦══════╦══════╦══════╦══════╗
║      ║ idx0 ║ idx1 ║ idx2 ║ idx3 ║
╠══════╬══════╬══════╬══════╬══════╣
║ idx0 ║      ║      ║      ║      ║
║ idx1 ║      ║    53232 ║
║ idx2 ║      ║      ║   2240 ║
║ idx3 ║      ║      ║      ║   13 ║
╚══════╩══════╩══════╩══════╩══════╝
  

Итак, тогда я просто создаю рекурсию и выполняю это следующим образом:

  public static void recur(int cost, int level, int idx) {
        if (level == 0) {
            if (min > cost   len[delka - idx]) {
                min = cost   len[delka - idx];
                System.out.println(min);
            }

        }
        if (level > 0 amp;amp; cost < min) {
            for (int i = idx; i <= delka - level; i  ) {
                recur(cost   d[idx][i], level - 1, i);                
        }
    }
  

Я начинаю с вызова его с 0 cost, level — это номер станции, оставшейся для сборки, а idx — указатель на последнюю строительную станцию.

Самая большая проблема заключается в том, что, например, для L = 200 и 50 станций существует 4.538583779232459 комбинаций e 47, и я не думаю, что у меня все хорошо получается, просматривая каждую комбинацию. Конечно, я что-то вырезал cost < min , но это все еще очень медленно, и я думаю, что я просто что-то упускаю.

У меня такое чувство, что я могу разбить это на подзадачи.

Оригинальная проблема на чешском языке: https://cw.felk.cvut.cz/courses/a4b33alg/task.php?task=servis

Ответ №1:

Во-первых, обратите внимание, что для любой позиции x, если мы уже вычислили результат, поместив последнюю станцию на y-й км, y < x, результат от x или справа не будет зависеть от выбора станций перед y. Таким образом, состояние решения DP будет [KM][position_of_last_station][station_already_builded]

Пример кода следующий:

 int memo[MAX_KM][MAX_KM][MAX_STATION];
bool seen[MAX_KM][MAX_KM][MAX_STATION]; // if seen[km][pos_of_last_station][station_remaining] is true 
                                         // then this subproblem has already been solved. No need to solve it again

int dp(int KM, int pos_of_last_station, int station_remaining) {
    if(KM == MAX_KM   1) {
        // reached the end
        int len = (MAX_KM - pos_of_last_station);
        return 2 * len * len   3 * len;
    }

    if(seen[KM][pos_of_last_station][station_remaining]) {
        // this sub problem has already been solved
        return memo[KM][pos_of_last_station][station_remaining];
    }
    int ret = 2e9; // some large value
    if(station_remaining > 0) {

        // trying establishing a station on the current position
        int len = KM - pos_of_last_station;
        ret = min(ret, dp(KM   1, KM, station_remaining - 1)   (2 * len * len   3 * len)   cost[KM] );  // assuming cost[KM] is the cost to establish a station on KMth kilometer
    }
    ret = min(ret, dp(KM   1, pos_of_last_station, station_remaining) );
    seen[KM][pos_of_last_station][station_remaining] = true; // sub problem visited
    memo[KM][pos_of_last_station][station_remaining] = ret;  // storing the result for future utilization

    return ret;

}
  

Анализ сложности: потребуется O (MAX_KM * MAX_KM * MAX_STATION) времени и пространства

Предупреждение: код не протестирован