Problemas de Mochila Dinâmica: 01, Completa, Múltipla e Agrupada

Introdução

Este artigo apresenta quatro tipos clássicos de problemas de mochila, organizados no seguinte mapa conceitual:

I. Mochila 01

Temos N itens e uma mochila com capacidade máxima M. O item i possui peso wi e valor vi. Dado o limite de peso da mochila, quais itens devem ser selecionados para maximizar o valor total?

Como cada item pode ser escolhido ou não exatamente uma vez, este problema é conhecido como Mochila 01.

Link do problema: AcWing 2. Problema da Mochila 01

#include <bits/stdc++.h>

using namespace std;

const int MAX_ITEMS = 1010;

int itemWeight[MAX_ITEMS], itemValue[MAX_ITEMS];
int dynamicTable[MAX_ITEMS][MAX_ITEMS];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int quantity, capacity;
    cin >> quantity >> capacity;
    
    for (int i = 1; i <= quantity; i++) {
        cin >> itemWeight[i] >> itemValue[i];
    }

    for (int i = 1; i <= quantity; i++) {
        for (int j = 1; j <= capacity; j++) {
            if (j < itemWeight[i]) {
                dynamicTable[i][j] = dynamicTable[i - 1][j];
            } else {
                dynamicTable[i][j] = max(
                    dynamicTable[i - 1][j],
                    dynamicTable[i - 1][j - itemWeight[i]] + itemValue[i]
                );
            }
        }
    }

    cout << dynamicTable[quantity][capacity] << "\n";

    return 0;
}

A complexidade temporal deste algoritmo é O(n × m).

1.1 Otimização com Array Unidimensional

Essa expressão é fundamental e será utilizada novamente quando abordarmos a mochila completa.

Na verdade, é incorreto afirmar isso. Se iterarmos j em ordem decrescente, o problema não ocorre.

#include <bits/stdc++.h>

using namespace std;

const int MAX_CAPACITY = 1010;

int itemWeight[MAX_CAPACITY], itemValue[MAX_CAPACITY];
int dp[MAX_CAPACITY];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int quantity, capacity;
    cin >> quantity >> capacity;
    
    for (int i = 1; i <= quantity; i++) {
        cin >> itemWeight[i] >> itemValue[i];
    }

    for (int i = 1; i <= quantity; i++) {
        for (int j = capacity; j >= itemWeight[i]; j--) {
            dp[j] = max(dp[j], dp[j - itemWeight[i]] + itemValue[i]);
        }
    }

    cout << dp[capacity] << "\n";

    return 0;
}

Além disso, os arrays itemWeight e itemValue podem ser eliminados, processando os dados durante a entrada. Isso resulta na versão otimizada final:

#include <bits/stdc++.h>

using namespace std;

const int MAX_CAPACITY = 1010;

int dp[MAX_CAPACITY];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int quantity, capacity;
    cin >> quantity >> capacity;

    for (int i = 1; i <= quantity; i++) {
        int peso, valor;
        cin >> peso >> valor;
        for (int j = capacity; j >= peso; j--) {
            dp[j] = max(dp[j], dp[j - peso] + valor);
        }
    }

    cout << dp[capacity];

    return 0;
}

Em resumo, quando a tabela dp é bidimensional, j pode ser percorrido em qualquer direção. Entretanto, quando usamos um array unidimensional, j deve ser percorrido de forma decrescente.

II. Mochila Compleat

Temos N tipos de itens e uma mochila com capacidade máxima M. Cada tipo de item possui quantidade ilimitada. O tipo i tem peso wi e valor vi. Dado o limite de peso da mochila, quais combinações de itens maximizam o valor total?

Link do problema: AcWing 3. Problema da Mochila Completa

#include <bits/stdc++.h>

using namespace std;

const int MAX_ITEMS = 1010;

int itemWeight[MAX_ITEMS], itemValue[MAX_ITEMS];
int dynamicTable[MAX_ITEMS][MAX_ITEMS];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int quantity, capacity;
    cin >> quantity >> capacity;
    
    for (int i = 1; i <= quantity; i++) {
        cin >> itemWeight[i] >> itemValue[i];
    }

    for (int i = 1; i <= quantity; i++) {
        for (int j = 1; j <= capacity; j++) {
            int maxQuantity = j / itemWeight[i];
            for (int k = 0; k <= maxQuantity; k++) {
                dynamicTable[i][j] = max(
                    dynamicTable[i][j],
                    dynamicTable[i - 1][j - k * itemWeight[i]] + k * itemValue[i]
                );
            }
        }
    }

    cout << dynamicTable[quantity][capacity] << "\n";

    return 0;
}

2.1 Otimização com Array Unidimensional

Com base na seção 1.1, esta iteração corresponde a percorrer j em ordem crescente. Portanto, basta modifciar a direção da iteração de j no código da Mochila 01.

#include <bits/stdc++.h>

using namespace std;

const int MAX_CAPACITY = 1010;

int dp[MAX_CAPACITY];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int quantity, capacity;
    cin >> quantity >> capacity;

    for (int i = 1; i <= quantity; i++) {
        int peso, valor;
        cin >> peso >> valor;
        for (int j = peso; j <= capacity; j++) {
            dp[j] = max(dp[j], dp[j - peso] + valor);
        }
    }

    cout << dp[capacity] << "\n";

    return 0;
}

A complexidade temporal após otimização permanece O(n × m).

III. Mochila Múltipla

Temos N tipos de itens e uma mochila com capacidade máxima M. O tipo i possui si unidades disponíveis, peso wi e valer vi. Dado o limite de peso da mochila, quais combinações maximizam o valor total?

Link do problema: AcWing 4. Problema da Mochila Múltipla

#include <bits/stdc++.h>

using namespace std;

const int MAX_N = 110;

int dp[MAX_N][MAX_N];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int quantity, capacity;
    cin >> quantity >> capacity;

    for (int i = 1; i <= quantity; i++) {
        int peso, valor, quantidade;
        cin >> peso >> valor >> quantidade;
        for (int j = 1; j <= capacity; j++) {
            int maxPossible = min(quantidade, j / peso);
            for (int k = 0; k <= maxPossible; k++) {
                dp[i][j] = max(
                    dp[i][j],
                    dp[i - 1][j - k * peso] + k * valor
                );
            }
        }
    }

    cout << dp[quantity][capacity] << "\n";

    return 0;
}

3.1 Otimização Binária

Link do problema: AcWing 5. Problema da Mochila Múltipla II

#include <bits/stdc++.h>

using namespace std;

const int MAX_ITEMS = 11010;
const int MAX_CAPACITY = 2010;

int weight[MAX_ITEMS], value[MAX_ITEMS];
int dp[MAX_CAPACITY];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int quantity, capacity;
    cin >> quantity >> capacity;

    int totalItems = 0;
    while (quantity--) {
        int peso, valor, qtde;
        cin >> peso >> valor >> qtde;
        for (int power = 1; power <= qtde; power *= 2) {
            totalItems++;
            weight[totalItems] = peso * power;
            value[totalItems] = valor * power;
            qtde -= power;
        }
        if (qtde > 0) {
            totalItems++;
            weight[totalItems] = peso * qtde;
            value[totalItems] = valor * qtde;
        }
    }

    quantity = totalItems;
    for (int i = 1; i <= quantity; i++) {
        for (int j = capacity; j >= weight[i]; j--) {
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
        }
    }

    cout << dp[capacity] << "\n";

    return 0;
}

IV. Mochila Agrupada

Temos N grupos de itens e uma mochila com capacidade máxima M. Cada grupo contém vários itens, porém no máximo um item de cada grupo pode ser selecionado. Cada item possui peso wij e valor vij, onde i representa o grupo e j representa o índice dentro do grupo. Dado o limite de peso da mochila, quais combinações maximizam o valor total?

Link do problema: AcWing 9. Problema da Mochila Agrupada

#include <bits/stdc++.h>

using namespace std;

const int MAX_GROUPS = 110;

int weight[MAX_GROUPS][MAX_GROUPS];
int value[MAX_GROUPS][MAX_GROUPS];
int groupSize[MAX_GROUPS];
int dp[MAX_GROUPS];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int quantity, capacity;
    cin >> quantity >> capacity;
    
    for (int i = 1; i <= quantity; i++) {
        cin >> groupSize[i];
        for (int j = 1; j <= groupSize[i]; j++) {
            cin >> weight[i][j] >> value[i][j];
        }
    }

    for (int i = 1; i <= quantity; i++) {
        for (int j = capacity; j >= 1; j--) {
            for (int k = 1; k <= groupSize[i]; k++) {
                if (j >= weight[i][k]) {
                    dp[j] = max(dp[j], dp[j - weight[i][k]] + value[i][k]);
                }
            }
        }
    }

    cout << dp[capacity] << "\n";

    return 0;
}

Conclusão

Podemos representar a Mochila 01, Completa e Múltipla através de uma fórmula unificada:

Tags: Algoritmos Dynamic Programming knapsack programação cpp

Publicado em 10-1 19:05