• 背包


    01背包

    这是最简单的一种背包,因为对于每一件物品都只有放和不放两种情况,故叫01背包。
    所以状态转换方程
    dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - c[i]] + v[i]);
    dp[i][j]指有i个物品,在j个空间中能存放的最大价值
    dp[i - 1][j]指第i个物品不放进背包,即和i- 1的情况相同
    dp[i - 1][j - c[i]] + v[i]指第i个物品放进背包,那么就是j个空间中减去第i个物品所占的空间c[i],这种情况下在再加上它的价值v[i]

    例题:
    描述
    辰辰是个很有潜能、天资聪颖的孩子,他的梦想是称为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”

    如果你是辰辰,你能完成这个任务吗?
    输入
    输入的第一行有两个整数T(1 <= T <= 1000)和M(1 <= M <= 100),T代表总共能够用来采药的时间,M代表山洞里的草药的数目。接下来的M行每行包括两个在1到100之间(包括1和100)的的整数,分别表示采摘某株草药的时间和这株草药的价值。
    输出
    输出只包括一行,这一行只包含一个整数,表示在规定的时间内,可以采到的草药的最大总价值。
    样例输入
    70 3
    71 100
    69 1
    1 2
    样例输出
    3

    代码

     1 #include<cstdio>
     2 #include<iostream>
     3 using namespace std;
     4 int dp[1005][1005], t[1005], v[1005];
     5 int T, M;
     6 int main()
     7 {
     8     scanf("%d%d", &T, &M);
     9     for(int i = 1; i <= M; ++i)
    10         scanf("%d%d", &t[i], &v[i]);
    11     for(int i = 0; i <= T; ++i) dp[0][i] = 0;   //初始化
    12     for(int i = 1; i <= M; ++i)
    13     {
    14         dp[i][0] = 0;       //初始化
    15         for(int j = T; j >= 0; --j)
    16         {
    17             dp[i][j] = dp[i - 1][j];
    18             if(j - t[i] >= 0)   //如果不判断,数组下标会出负数
    19                 dp[i][j] = max(dp[i][j], dp[i - 1][j - t[i]] + v[i]);
    20         }
    21     }
    22     printf("%d\n", dp[M][T]);
    23     return 0;
    24 }


    以上方法的时间和空间复杂度均为O(N*V),其中时间复杂度基本已经不能再优化了,但空间复杂度却可以优化到O(V),即改成一维数组
    #include<cstdio>
    #include<iostream>
    using namespace std;
    int dp[1005], t[1005], v[1005];
    int T, M;
    int main()
    {
        scanf("%d%d", &T, &M);
        for(int i = 1; i <= M; ++i)
            scanf("%d%d", &t[i], &v[i]);
        for(int i = 1; i <= M; ++i)
            for(int j = T; j >= t[i]; j--)
                    dp[j] = max(dp[j], dp[j - t[i]] + v[i]);
       printf("%d\n",dp[T]);
        return 0;
    }

    其中内层循环最好要从后往前,因为dp[j]一定是从前面更新来的。若是正着来,那么一个物品就可能被放进去多次。

    多重背包


    多重背包就是每一件物品都有多个(但是并不是无限的),求最大价值。

    思路:转换为01背包。如:有容量为3,价值为5的物品12个,那么就可看成有12个物品。但时间复杂度会相当的大。

    改进:
    先看一道题:一个数n=63,将n分成6个数之和,使这6个数可以组合成从0到n之间任意的一个数,问这6个数是多少。
    分析:我们发现,2^6-1=63,即63的二进制为111111,所以6个数分别为1,2,4,8,16,32。根据二进制运算,这几个数可组成0到111111之间任意一个数。
    那么若n=60怎么办呢?
    其实就是把最后一个数32-3。

    所以对于多重背包问题,相同种类的物品就可进行合并:个数不断减2^n(n++),直到不够减为止,则剩下的就是最后一个数。
    如:有容量为3,价值为5的物品12个。因为12=1+2+4+5,所以可转换为4个容量和价值分别为(3,5),(6,10),(12,20),(15,25)的物品。

    代码

     1 #include<cstdio>
     2 #include<iostream>
     3 using namespace std;
     4 const int maxn = 10005;
     5 int dp[maxn], c[maxn], v[maxn]; //c为容量,v为价值
     6 int m, n, q = 0; //m为背包体积,n为物品种类, q记录合并后的物品数 
     7 void hebin(int C, int V,int GS)
     8 {
     9     int x = 1;
    10     while(GS - x > 0)
    11     {
    12         c[++q] = x * C;
    13         v[q] = x * V;
    14         GS -= x;
    15         x *= 2;
    16     }
    17     c[++q] = GS * C;
    18     v[q] = GS * V;
    19     return;
    20 }
    21 int main()
    22 {
    23     scanf("%d%d", &m ,&n); 
    24     for(int i = 1; i <= n; ++i)
    25     {
    26         int cc, vv, gs;    //gs为一种物品的个数
    27         scanf("%d%d%d", &cc, &vv, &gs);
    28         hebin(cc, vv, gs); 
    29     }
    30     for(int i = 1; i <= q; ++i)
    31     {
    32         for(int j = m; j >= 0; --j)
    33         {
    34             if(j - c[i] >= 0)
    35                 dp[j] = max(dp[j], dp[j - c[i]] + v[i]);
    36         }
    37     }
    38     printf("%d\n", dp[m]);
    39     return 0;
    40 }

    如此就将第i种物品分成了O(logn[i])种物品,将原问题转化为了复杂度为O(V*Σlog n[i])的01背包问题,是很大的改进

    完全背包


    其实就是每个物品有无限个,求最大价值。

    思路:转化为多重背包,进而01背包。
    可用所给的背包最大容量来确定每个物品的最大数量。
    如:背包容量为10,有一个物品所占容量为3,那么它最多有10/3=3个。
    这样就转化成了多重背包。
    代码

     1 #include<cstdio>
     2 #include<iostream>
     3 using namespace std;
     4 const int maxn = 10005;
     5 int dp[maxn], c[maxn], v[maxn];
     6 int m, n, q = 0;
     7 void hebin(int C, int V, int M) //就这和多重背包不一样,M是总容量
     8 {
     9     int x = 1;
    10     while(M - x * C >= 0)
    11     {
    12         c[++q] = x * C;
    13         v[q] = x * V;
    14         M -= x * C;
    15         x *= 2;
    16     }
    17     if(M >= C)  //处理余下的同类物品
    18     {
    19         c[++q] = M / C * C;
    20         v[q] = M / C * V;
    21     }
    22     return;
    23 }
    24 int main()
    25 {
    26     scanf("%d%d", &m, &n);
    27     for(int i = 1; i <= n; ++i)
    28     {
    29         int cc, vv;
    30         scanf("%d%d", &cc, &vv);
    31         hebin(cc, vv, m); 
    32     }
    33     for(int i = 1; i <= q; ++i)
    34         printf("c = %d v = %d\n", c[i], v[i]);
    35     for(int i = 1; i <= q; ++i)
    36     {
    37         for(int j = m; j >= 0; --j)
    38         {
    39             if(j - c[i] >= 0)
    40                 dp[j] = max(dp[j], dp[j - c[i]] + v[i]);
    41         }
    42     }
    43     printf("%d\n", dp[m]);
    44     return 0;
    45 }

    其实基本思路并不是这个。
    不过别担心,这种解法比基本思路要快。但是为了完整性,我还是讲讲基本思路吧。
    先上段伪代码

    1 for(i...n)
    2     for(j...m)        //m是背包容量
    3     {                //c[]是物品容量,v[]是物品价值 
    4         if(j - c[i] > 0)     
    5             dp[j] = max(dp[j], dp[j - c[i]] + v[i])
    6     }
    7 printf("%d\n", dp[m]); 

    觉得熟悉?
    没错,它和01背包就差个内层循环,一个正着,一个倒着。那为什么这么一改,就从01背包转化为完全背包了呢?
    首先我们要清楚的是,因为dp[j] = dp[j] 或 dp[j - c[i]] + v[i] ,所以每一次dp,要么保持不变,要么用前面的更新后面的。如果从0到m循环,也就可能导致在dp前i个物品时第i个物品被放进去了多次,那就不满足每一个物品只有1个,即01背包了。相反的,这不就是完全背包的解法吗?

    水一段代码

    #include<cstdio>
    #include<iostream>
    using namespace std;
    int dp[1005], c[1005], v[1005];
    int m, n;
    int main()
    {
        scanf("%d%d", &m, &n);
        for(int i = 1; i <= n; ++i)
            scanf("%d%d", &c[i], &v[i]);
        for(int i = 1; i <= n; ++i) //是的,就这改了
        {
            for(int j = 0; j <= m ; ++j)
            {
                if(j - c[i] >= 0)
                    dp[j] = max(dp[j], dp[j - c[i]] + v[i]);
            }
        }
        printf("%d\n",dp[m]);
        return 0;
    }

    分组背包


    有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。这些物品被划分为若干组,每组中的物品互相冲突,最多选一件。

    思路:对于每一组,只有选其中一件,或什么都不选两种选择。
    不选: dp[v] = dp[v]
    选一组中的第i个:dp[v] = dp[v - c[i]] + w[i]
    将两者比较,取最大
    (v指当前容量为v的情况下)

    伪代码:

    1 for 所有的组k
    2     for v=V..0
    3         for 所有的i属于组k
    4             f[v]=max{f[v],f[v-c[i]]+w[i]}

    例题:P1757 通天之分组背包
    题目描述

    自01背包问世之后,小A对此深感兴趣。一天,小A去远游,却发现他的背包不同于01背包,他的物品大致可分为k组,每组中的物品相互冲突,现在,他想知道最大的利用价值是多少。

    输入输出格式

    输入格式:
    两个数m,n,表示一共有n件物品,总重量为m

    接下来n行,每行3个数ai,bi,ci,表示物品的重量,利用价值,所属组数

    输出格式:
    一个数,最大的利用价值
    上代码

     1 #include<cstdio>
     2 #include<iostream>
     3 #include<cmath>
     4 #include<algorithm>
     5 using namespace std;
     6 const int maxn = 1005;
     7 int m, n, maxk = -1;        //m为背包容量,n为物品数
     8 int dp[maxn], c[105][maxn], v[105][maxn], zu[105];
     9 int main()                  //zu[]记录每一组中有多少个物品
    10 {
    11     scanf("%d%d", &m, &n);
    12     for(int i = 1; i <= n; ++i)
    13     {
    14         int ci, vi, k;
    15         scanf("%d%d%d", &ci, &vi, &k);
    16         c[k][++zu[k]] = ci; v[k][zu[k]] = vi;
    17         if(k > maxk) maxk = k;  //记录最大组数
    18     }
    19     for(int i = 1; i <= maxk; ++i)
    20         for(int j = m; j >= 0; --j)
    21             for(int q = 1; q <= zu[i]; ++q)
    22                 if(j - c[i][q] >= 0)
    23                     dp[j] = max(dp[j], dp[j - c[i][q]] + v[i][q]);
    24     printf("%d\n", dp[m]);
    25     return 0;
    26 }
  • 相关阅读:
    咖啡生活-享受慵懒的午后时光
    窗体皮肤实现
    编译树莓派2代B型OpenWrt固件实现无线路由器及nodogsplash认证功能
    通过hook实现禁止shift+delete快捷键
    c++ Qt向PHP接口POST文件流
    Qt for Android开发总结
    A previous installation of Qt5 Visual Studio Add-in was detected. Please uninstall it before running this installer解决办法
    windows下Qt5.1 for android开发环境配置
    Qt+mpg123+openal播放MP3流
    RTC搭建android下三层应用程序访问服务器MsSql-客户端
  • 原文地址:https://www.cnblogs.com/mrclr/p/8119792.html
Copyright © 2020-2023  润新知