• 01背包问题:POJ3624


    背包问题是动态规划中的经典问题,而01背包问题是最基本的背包问题,也是最需要深刻理解的,否则何谈复杂的背包问题。

    POJ3624是一道纯粹的01背包问题,在此,加入新的要求:输出放入物品的方案。

    我们的数组基于这样一种假设:

    totalN表示物品的种类,totalW表示背包的容量

    w[i]表示第i件物品的重量,d[i]表示第i件物品的价值。

    F(i,j)表示前i件物品放入容量为j的背包中,背包内物品的最大价值。

    F(i,j) = max{ F(i-1,j) , F(i-1,j-w[i])+d[i] }

    我们仅考虑第i件物品到底放不放进背包

    第一项表示不放入背包的情况。

    第二项表示放入背包的情况。

    然后,我们为了获得具体的方案,每当在局部最优的方案成立时,则将path[i][j]置为1,当求的最优结果后,从最终结果开始回溯,看看在第i轮的局部最优中是否放入物品i。

    此时的代码如下所示:

    #include <stdio.h>
    #include <string.h>
    #include <string>
    #include <iostream>
    using namespace std;
    #define max(a,b) a>b?a:b;
    //数组要设的比给的范围稍大一些
    int dp[3410][12900];
    int path[3410][12900];
    int w[3410];
    int d[3410];
    int totalN;
    int totalW;
    int main()
    {
        int i,j;
        scanf("%d",&totalN);
        scanf("%d",&totalW);
    
        for(i=1;i<=totalN;i++)
        {
            scanf("%d",&w[i]);
            scanf("%d",&d[i]);
        }
    
        memset(dp,0,sizeof(dp));
        for(i=1;i<=totalN;i++){
            for(j=1;j<=totalW;j++){
                /*必不可少,需要根据上一轮状态,
                而如果直接从j=w[i]开始,会使0<j<w[i]都为0,影响正确的结果。
                在之后优化的1维矩阵方法中,就不会存在这种问题,
                由于其逆序遍历,上一轮的结果只要不覆盖,就一直存在*/
                dp[i][j] = dp[i-1][j];
                if(j>=w[i] && dp[i][j]<dp[i-1][j-w[i]]+d[i]){
                    dp[i][j] =dp[i-1][j-w[i]]+d[i];
                    path[i][j] = 1;
                }
            }
        }
        printf("背包内物品的最大价值是:
    ");
        printf("%d
    ",dp[totalN][totalW]);
        i = totalN;
        j = totalW;
        printf("放入背包的物品是:
    ");
        while(i>0 && j>0)
        {
            if(path[i][j] == 1)
            {
                printf("%d
    ",d[i]);
                j = j - w[i];
            }
            i = i - 1;
        }
    
        system("pause");
        return 0;
    }

    此时的时间复杂度是O(NW),已经无法优化,但空间复杂度可以由O(NW)下降到O(W).。

    我们可以看到F(i,j)仅仅依赖于F(i-1,j)和F(i-1,j-w[i])+d[i],也就是说,我们需要依赖的上一轮的序列中的元素的坐标没有在J的右边的,此时我们就可以使用逆序遍历即可,直接应用上一轮的数据。

    这样的逆序遍历还有一层意义,即每个物品只放入一次,因为所以来的两项都是没有放入过第i件物品的情况,从不依赖放入第i件物品的情况。这点需要好好的理解一下,有助于后续对完全背包问题的理解。

    上代码:

    #include <stdio.h>
    #include <string.h>
    #include <string>
    #include <iostream>
    using namespace std;
    //数组要设的比给的范围稍大一些
    int bag[12900];
    int path[3410][12900];
    int w[3410];
    int d[3410];
    int totalN;
    int totalW;
    int main()
    {
        int i,j;
        scanf("%d",&totalN);
        scanf("%d",&totalW);
    
        for(i=1;i<=totalN;i++)
        {
            scanf("%d",&w[i]);
            scanf("%d",&d[i]);
        }
    
        memset(bag,0,sizeof(bag));
        for(i=1;i<=totalN;i++){
            for(j=totalW;j>=w[i];j--){
                if(bag[j]<bag[j-w[i]]+d[i]){
                    bag[j] = bag[j-w[i]]+d[i];
                    path[i][j]=1;
                }
            }
        }
        printf("背包内物品的最大价值是:
    ");
        printf("%d
    ",bag[totalW]);
        i = totalN;
        j = totalW;
        printf("放入背包的物品是:");
        while(i>0 && j>0)
        {
            if(path[i][j] == 1)
            {
                printf("%d
    ",d[i]);
                j = j - w[i];
            }
            i = i - 1;
        }
    
        system("pause");
        return 0;
    }

    最后如果想去AC的话,要采用第二种方案,降低空间复杂度,AC代码(折叠):

    #include <stdio.h>
    #include <string.h>
    #include <string>
    #include <iostream>
    using namespace std;
    //数组要设的比给的范围稍大一些
    int bag[12900];
    int w[3410];
    int d[3410];
    int totalN;
    int totalW;
    int main()
    {
        int i,j;
        scanf("%d",&totalN);
        scanf("%d",&totalW);
    
        for(i=1;i<=totalN;i++)
        {
            scanf("%d",&w[i]);
            scanf("%d",&d[i]);
        }
        memset(bag,0,sizeof(bag));
        for(i=1;i<=totalN;i++){
            for(j=totalW;j>=w[i];j--){
                if(bag[j]<bag[j-w[i]]+d[i]){
                    bag[j] = bag[j-w[i]]+d[i];
                }
            }
        }    
        printf("%d
    ",bag[totalW]);
        system("pause");
        return 0;
    }
    View Code
  • 相关阅读:
    C# 翻页设计:首页,上一页,下一页,末页 ,跳转
    sqlsever2008数据库的备份与还原
    解决treeview的同一节点单击多次的执行问题
    juery mobile select下来菜单选项提交form问题
    利用 lucene.net 实现高效率的 WildcardQuery ,记一次类似百度搜索下拉关键字联想功能的实现。
    字符编码笔记:ASCII,Unicode和UTF-8 转
    由三目运算符 == ? : 引起的一个问题,醉了,基础不过关。记录一下,比较简单的一个问题,只是为了记录一下
    记一次排错,windows日志 模块 DLL C:Windowssystem32inetsrvaspnetcore.dll 未能加载。返回的数据为错误信息。
    windows xp 连接USB网络打印机服务器(通用所有usb网络打印机服务器的安装)
    try catch中用了 Response.Redirect 引发的线程异常终止
  • 原文地址:https://www.cnblogs.com/yueyanglou/p/5353124.html
Copyright © 2020-2023  润新知