Multidimensional backpack with minimum and maximum

I have a problem that looks like a satchel problem, more specifically, a multidimensional change .

I have a bunch of objects that have value, cost and category. I need to optimize the knapsack at cost at maximum cost, but also have a certain number of objects in each category.

I successfully implemented in C ++ the original knapsack algorithm, ignoring the categories.

When I tried to add categories, I realized that I could just see this as a multidimensional knapsack problem, each of which has a weight of 0 or 1 in a new dimension.

My main problem is that I have not only a maximum, for example: 5 objects of the food type, but also a minimum, since I need exactly 5 objects of the food type.

And I can’t figure out how to add a minimum to the algorithm.

Obviously, I can use the general case where each dimension has a maximum and a minimum, and optimize for the general, since all my measurements, except for one, have a range of only 1, so in the end it will still be optimized for the value. In addition, I can set a minimum value for zero to avoid a single measurement without a minimum, and it will still work.

I work in C ++, but to be honest, even the pseudo-code will be fine, I just need an algorithm.

, , , , , .

. , , . .

, 100 , 1000 , , . 10 , 15 5 . ( 0) . , .

, , , , " " . () .

+4
2

, , . , . N , [v_i x_i] [w_i x_i] W, x_i 0 1 ( ). , sum [x_i] = N. , , , .

: 0/1 :

#include <cstdio>
#include <iostream>
#include <vector>
#include <map>
#include <algorithm>

using uint = unsigned int;

template <typename T>
struct item {
    T value;
    uint weight;
};

template <typename T>
T knapSack(uint W, const std::vector< item<T> >& items) {

    std::map< std::pair<uint, uint>, T> cache;

    std::function<T(uint, uint)> recursion;
    recursion = [&] (uint n, uint w) {
        if (n == 0)
            return 0;
        auto it = cache.find(std::make_pair(n,w));
        if (it != cache.end())
            return it->second;
        T _v = items[n-1].value;
        uint _w = items[n-1].weight;
        T nextv;
        if (_w <= w)
            nextv = std::max(_v + recursion(n-1,w-_w),recursion(n-1,w));
        else
            nextv = recursion(n-1,w);
        cache.insert(std::make_pair(std::make_pair(n,w),nextv));
        return nextv;   
    };

    return recursion(items.size(),W);
}

( -) . < N < W < N-1 < W N-1 < N-1 < W - w [N-1].

, . , , 0 1 , , , , , .. K < N < W K < N-1 < W N-1 K-1 < N-1 < W - w [N-1]. - , K < N, K > N. , 0, , "", . , , -, . . K > N 0, :

template <typename T>
std::pair<T,bool> knapSackConstrained(uint W, uint K, const std::vector< item<T> >& items) {

    std::map< std::tuple<uint, uint, uint>, std::pair<T,bool> > cache;

    std::function<std::pair<T, bool>(uint, uint, uint)> recursion;
    recursion = [&] (uint n, uint w, uint k) {
        if (k > n)
            return std::make_pair(0,false);
        if (n == 0 || k == 0)
            return std::make_pair(0,true);
        auto it = cache.find(std::make_tuple(n,w,k));
        if (it != cache.end())
            return it->second;
        T _v = items[n-1].value;
        uint _w = items[n-1].weight;
        T nextv;
        bool nextvalid = true;
        if (_w <= w) {
            auto take = recursion(n-1,w-_w,k-1);
            auto reject = recursion(n-1,w,k);
            if (take.second and reject.second) {
                nextv = std::max(_v + take.first,reject.first);
            } else if (take.second) {
                nextv = _v + take.first;
            } else if (reject.second) {
                nextv = reject.first;
            } else {
                nextv = 0;
                nextvalid = false;
            }   
        } else {
            std::tie(nextv,nextvalid) = recursion(n-1,w,k);
        }
        std::pair<T,bool> p = std::make_pair(nextv,nextvalid);
        cache.insert(std::make_pair(std::make_tuple(n,w,k),p));
        return p;   
    };

    return recursion(items.size(),W,K);
}

, :

int main(int argc, char *argv[]) {
    std::vector< item<int> > items = {{60,10},{10,6},{10,6}};
    int  j = 13;
    std::cout << "Unconstrained: " << knapSack(j,items) << std::endl;
    for (uint k = 1; k <= items.size(); ++k) {
        auto p = knapSackConstrained(j,k,items);
        std::cout << "K = " << k << ": " << p.first;
        if (p.second)
            std::cout << std::endl;
        else
            std::cout << ", no valid solution" << std::endl;
    }
    return 0;
}

% OUTPUT %
Unconstrained: 60
K = 1: 60
K = 2: 20
K = 3: 0, no valid solution

3 , , , .

, , . , , , . , , , - . K , item . K > N ( ), , , , (i-1) - W, , , 1 , (i-1) st-.

#include <cstdio>
#include <iostream>
#include <vector>
#include <map>
#include <algorithm>

using uint = unsigned int;

template <typename T>
struct item {
    T value;
    uint weight;
    uint category;
};

template <typename T>
std::pair<T,bool> knapSack(uint W, const std::vector<uint>& K, const std::vector< item<T> >& items) {

    std::map< std::tuple<uint, uint, std::vector<uint> >, std::pair<T,bool> > cache;

    std::function<std::pair<T, bool>(uint, uint, std::vector<uint>)> recursion;
    recursion = [&] (uint n, uint w, std::vector<uint> k) {

        auto it = cache.find(std::make_tuple(n,w,k));
        if (it != cache.end())
            return it->second;

        std::vector<uint> ccount(K.size(),0);
        for (uint c = 0; c < K.size(); ++c) {
            for (uint i = 0; i < n; ++i) {
                if (items[i].category == c)
                    ++ccount[c];
            }
        }
        for (uint c = 0; c < k.size(); ++c) {
            if (k[c] > ccount[c]) {
                auto p = std::make_pair(0,false);
                cache.insert(std::make_pair(std::make_tuple(n,w,k),p));
                return p;
            }
        }

        uint sumk = 0; for (const auto& _k : k) sumk += _k;
        if (n == 0 || sumk == 0) {
            auto p = std::make_pair(0,true);
            cache.insert(std::make_pair(std::make_tuple(n,w,k),p));
            return p;
        }

        T _v = items[n-1].value;
        uint _w = items[n-1].weight;
        uint _c = items[n-1].category;
        T nextv;
        bool nextvalid = true;
        if (_w <= w and k[_c] > 0) {
            std::vector<uint> subk = k;
            --subk[_c];
            auto take = recursion(n-1,w-_w,subk);
            auto reject = recursion(n-1,w,k);
            if (take.second and reject.second) {
                nextv = std::max(_v + take.first,reject.first);
            } else if (take.second) {
                nextv = _v + take.first;
            } else if (reject.second) {
                nextv = reject.first;
            } else {
                nextv = 0;
                nextvalid = false;
            }   
        } else {
            std::tie(nextv,nextvalid) = recursion(n-1,w,k);
        }
        std::pair<T,bool> p = std::make_pair(nextv,nextvalid);
        cache.insert(std::make_pair(std::make_tuple(n,w,k),p));
        return p;   
    };

    return recursion(items.size(),W,K);
}

int main(int argc, char *argv[]) {
    std::vector< item<int> > items = {{60,10,0}, {100,20,1}, {120,30,0}, {140,35,1}, {145,40,0}, {180,45,1}, {160,50,1}, {170,55,0}};
    int  j = 145;
    for (uint k1 = 0; k1 <= items.size(); ++k1) {
        for (uint k2 = 0; k2 <= items.size(); ++k2) {
            auto p = knapSack(j,std::vector<uint>({k1,k2}),items);
            if (p.second)
                std::cout << "K0 = " << k1 << ", K1 = " << k2 << ": " << p.first << std::endl;
        }
    }
    return 0;
}

% OUTPUT (with comments) %

K0 = 0, K1 = 0: 0
K0 = 0, K1 = 1: 180 // e.g. {} from 0, {180} from 1
K0 = 0, K1 = 2: 340 // e.g. {} from 0, {160,180} from 1
K0 = 0, K1 = 3: 480 // e.g. {} from 0, {140,160,180} from 1
K0 = 1, K1 = 0: 170 // e.g. {170} from 0, {} from 1
K0 = 1, K1 = 1: 350 // e.g. {170} from 0, {180} from 1
K0 = 1, K1 = 2: 490 // e.g. {170} from 0, {140, 180} from 1
K0 = 1, K1 = 3: 565 // e.g. {145} from 0, {100, 140, 180} from 1
K0 = 2, K1 = 0: 315 // e.g. {145,170} from 0, {} from 1
K0 = 2, K1 = 1: 495 // e.g. {145,170} from 0, {180} from 1
K0 = 2, K1 = 2: 550 // e.g. {60,170} from 0, {140,180} from 1
K0 = 2, K1 = 3: 600 // e.g. {60,120} from 0, {100,140,180} from 1
K0 = 3, K1 = 0: 435 // e.g. {120,145,170} from 0, {} from 1
K0 = 3, K1 = 1: 535 // e.g. {120,145,170} from 0, {100} from 1
K0 = 3, K1 = 2: 605 // e.g. {60,120,145} from 0, {100,180} from 1
K0 = 4, K1 = 0: 495 // e.g. {60,120,145,170} from 0, {} from 1

, , , [ ]. , , .

, , - . - std::set<std::size_t> , recursion knapSack, , . , , . , , - , . , :

#include <cstdio>
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <algorithm>

using uint = unsigned int;

template <typename T>
struct item {
    T value;
    uint weight;
    uint category;
};

template <typename T>
std::tuple<T,bool,std::set<size_t> > knapSack(uint W, std::vector<uint> K, const std::vector< item<T> >& items) {

    std::map< std::tuple<uint, uint, std::vector<uint> >, std::tuple<T,bool,std::set<std::size_t> > > cache;

    std::function<std::tuple<T,bool,std::set<std::size_t> >(uint, uint, std::vector<uint>&)> recursion;

    recursion = [&] (uint n, uint w, std::vector<uint>& k) {

        auto it = cache.find(std::make_tuple(n,w,k));
        if (it != cache.end())
            return it->second;

        std::vector<uint> ccount(K.size(),0);
        for (uint i = 0; i < n; ++i) {
            ++ccount[items[i].category];
        }

        for (uint c = 0; c < k.size(); ++c) {
            if (k[c] > ccount[c]) {
                auto p = std::make_tuple(0,false,std::set<std::size_t>{});
                cache.insert(std::make_pair(std::make_tuple(n,w,k),p));
                return p;
            }
        }
        uint sumk = 0; for (const auto& _k : k) sumk += _k;
        if (n == 0 || sumk == 0) {
            auto p = std::make_tuple(0,true,std::set<std::size_t>{});
            cache.insert(std::make_pair(std::make_tuple(n,w,k),p));
            return p;
        }

        T _v = items[n-1].value;
        uint _w = items[n-1].weight;
        uint _c = items[n-1].category;
        T nextv;
        bool nextvalid = true;
        std::set<std::size_t> nextset;
        if (_w <= w and k[_c] > 0) {
            --k[_c];
            auto take = recursion(n-1,w-_w,k);
            ++k[_c];
            auto reject = recursion(n-1,w,k);
            T a = _v + std::get<0>(take);
            T b = std::get<0>(reject);
            if (std::get<1>(take) and std::get<1>(reject)) {
                nextv = std::max(a,b);
                if (a > b) {
                    nextset = std::get<2>(take);
                    nextset.insert(n-1);
                } else {
                    nextset = std::get<2>(reject);
                }
            } else if (std::get<1>(take)) {
                nextv = a;
                nextset = std::get<2>(take);
                nextset.insert(n-1);
            } else if (std::get<1>(reject)) {
                nextv = b;
                nextset = std::get<2>(reject);
            } else {
                nextv = 0;
                nextvalid = false;
                nextset = {};
            }   
        } else {
            std::tie(nextv,nextvalid,nextset) = recursion(n-1,w,k);
        }
        auto p = std::make_tuple(nextv,nextvalid,nextset);
        cache.insert(std::make_pair(std::make_tuple(n,w,k),p));
        return p;   
    };

    return recursion(items.size(),W,K);
}

int main(int argc, char *argv[]) {
    std::vector< item<int> > items = {{60,10,0}, {100,20,1}, {120,30,0}, {140,35,1}, {145,40,0}, {180,45,1}, {160,50,1}, {170,55,0}};
    int  j = 145;
    for (uint k1 = 0; k1 <= items.size(); ++k1) {
        for (uint k2 = 0; k2 <= items.size(); ++k2) {
            auto p = knapSack(j,std::vector<uint>({k1,k2}),items);
            if (std::get<1>(p)) {
                std::cout << "K0 = " << k1 << ", K1 = " << k2 << ": " << std::get<0>(p);
                std::cout << "; contents are {";
                for (const auto& index : std::get<2>(p))
                    std::cout << index << ", ";
                std::cout << "}" << std::endl;
            }
        }
    }
    return 0;
}

K0 = 0, K1 = 0: 0; contents are {}
K0 = 0, K1 = 1: 180; contents are {5, }
K0 = 0, K1 = 2: 340; contents are {5, 6, }
K0 = 0, K1 = 3: 480; contents are {3, 5, 6, }
K0 = 1, K1 = 0: 170; contents are {7, }
K0 = 1, K1 = 1: 350; contents are {5, 7, }
K0 = 1, K1 = 2: 490; contents are {3, 5, 7, }
K0 = 1, K1 = 3: 565; contents are {1, 3, 4, 5, }
K0 = 2, K1 = 0: 315; contents are {4, 7, }
K0 = 2, K1 = 1: 495; contents are {4, 5, 7, }
K0 = 2, K1 = 2: 550; contents are {0, 3, 5, 7, }
K0 = 2, K1 = 3: 600; contents are {0, 1, 2, 3, 5, }
K0 = 3, K1 = 0: 435; contents are {2, 4, 7, }
K0 = 3, K1 = 1: 535; contents are {1, 2, 4, 7, }
K0 = 3, K1 = 2: 605; contents are {0, 1, 2, 4, 5, }
K0 = 4, K1 = 0: 495; contents are {0, 2, 4, 7, }

, , psuedo-, .

+2

, Pseudo Code , , , , , , , :

Knapsack; , , . , - , , . . , , , .

, K KV, {KV1, KV2, ... KVn}, , value, weight category or classification , weight , . , [min, max] , category classification. , , KV, elements, , , , linear to polynomial - time and space complexity, quadratic and/or exponential - time and space complexities.

, , , , .., , .

0

Source: https://habr.com/ru/post/1673733/


All Articles