动态规划4-完全背包

有n种物品,每种物品的体积是ci,价值是wi,数量不限,现在有一个容积是v的背包,如何装物品才能使价值最大。

这个题与0-1背包很像,或者说是0-1背包的升级版。原来物品有两个状态可选,0和1;现在有v/ci+1个状态可选,0(不装),1(装一个),2(装两个)……v/ci(装v/ci个)。

那么把0-1背包的推导方程扩展一下就变成了

 

k的取值是0和1的时候,就是0-1背包问题。我们能不能用0-1背包的思路呢?答案是肯定的。下面是递归调用的方法

void zobag(vector<int>& weights, vector<int>& wealths, int vbag, int index, map<int, int>& selmap)
{
    if (index < 0)
    {
        return;
    }
    else
    {
        map<int, int> selmap2 = selmap;
        for (int i = 0; i <= vbag / weights[index]; i++)
        {
            map<int, int> selmap1 = selmap;
            if (vbag >= i * weights[index])
            {
                selmap1[index] = i;
            }
            zobag(weights, wealths, vbag - i * weights[index], index - 1, selmap1);
            int tmp1 = 0;
            int tmp2 = 0;
            //int tmpw1 = 0;
            for (auto& iter : selmap1)
            {
                int a = iter.second;
                while (a > 0)
                {
                    tmp1 += wealths[iter.first];
                    //tmpw1 += weights[iter.first];
                    a--;
                }
            }
            for (auto& iter : selmap2)
            {
                int a = iter.second;
                while (a > 0)
                {
                    tmp2 += wealths[iter.first];
                    a--;
                }
            }
            /*if (tmpw1 != 10)
            {
                tmp1 = 0;
                selmap1.clear();
            }*/
            if (tmp1 > tmp2)
            {
                selmap2 = selmap1;
            }
        }
        selmap = selmap2;
    }
}
int main()
{
    int vbag = 10;
    vector<int> weights = { 5, 3, 4, 3, 5 };
    vector<int> wealths = { 500, 200, 300, 350, 400 };
    map<int, int> selmap;
    zobag(weights, wealths, vbag, weights.size() - 1, selmap);
    for (auto& iter : selmap)
    {
        int a = iter.second;
        while (a > 0)
        {
            cout << iter.first << endl;
            a--;
        }
    }
    char inchar;
    cin >> inchar;
}

输出是3 3 3,也就是索引是3(第四种物品)放3个。我们看到第四种物品是350/3,所以放三个是9的空间,小于给定的10,并且是最大的。

我们把循环的条件从vbag/weights[index]改成1,就是0-1背包。

注释的部分是另一种问题,就是求如果必须要求正好装满背包,不能多,也不能少,那么输出就是0 0。只要我们这个写好了,其他的都是增加额外的判断条件就好了。

如果单单是完全背包问题,可以进行优化,就是把同体积价值小的去掉;或者在转成0-1背包问题的时候,把物品改成w*2^k形式。比如物品是500/3(价值500,体积3),原来拆分是改成多个500/3,现在可以改成500/3 1000/6……这样的话可以减少递归的次数,因为原来计算两次500/3,现在只需计算一次1000/6就可以。

 

这样总感觉不是特别好,那么我们原来动态规划的方法可以用吗?答案也是肯定的

 

int main()
{
    int vbag = 10;
    vector<int> weights = { 5, 3, 4, 3, 5 };
    vector<int> wealths = { 500, 200, 300, 350, 400 };
    int* dp = new int[(10 + 1) * 5]();//多一个,方便用索引直接表示内容
    int maxdp = 0;
    int maxi = 0;
    int maxj = 0;
    for (int vbgi = 0; vbgi < 10 + 1; vbgi++)
    {
        for (int i = 0; i < 5; i++)
        {
            if (vbgi == 0)
            {
                *(dp + vbgi * 5 + i) = 0;
            }
            else
            {
                int optw = 0;
                int wi = 0;
                while (vbgi >= weights[i] * wi)
                {
                    int tmpoptw = wealths[i] * wi;
                    if (i - 1 >= 0)
                    {
                        tmpoptw = tmpoptw + *(dp + (vbgi - weights[i] * wi) * 5 + i - 1);
                    }
                    if (tmpoptw > optw)
                    {
                        optw = tmpoptw;
                    }
                    wi++;
                }
                if (optw > maxdp)
                {
                    maxdp = optw;
                    maxi = i;
                    maxj = vbgi;
                }
                //这里注意赋值
                *(dp + vbgi * 5 + i) = optw;
            }
        }
    }
    while (*(dp + maxj * 5 + maxi) > 0 && maxi >= 0 && maxj >= 0)
    {
        //这里需要遍历找到第一个满足的条件,因为后面的会取前面的最大值
        for (int i = 0; i < maxi; i++)
        {
            if (*(dp + maxj * 5 + i) == *(dp + maxj * 5 + maxi))
            {
                maxi = i;
                break;
            }
        }
        //这里注意从1开始遍历,因为表示装了几个
        for (int i = 1; i <= *(dp + maxj * 5 + maxi) / wealths[maxi]; i++)
        {
            cout << wealths[maxi] << endl;
        }
        maxj = maxj - *(dp + maxj * 5 + maxi) / wealths[maxi] * weights[maxi];
        maxi--;
    }
    char inchar;
    std::cin >> inchar;
}

动态规划就是在原来的基础上加上一个循环,把一个物品多次加入就可以了。

posted @ 2020-01-14 10:25  秋来叶黄  阅读(166)  评论(0编辑  收藏  举报