zoukankan      html  css  js  c++  java
  • 动态规划_背包问题笔记

    https://www.cnblogs.com/31415926535x/p/10403589.html

    dp自从知道有这么个东西时,就没有好好的学,,现在一看道dp的题就绕道走,,,但是,很多比赛中的dp问题有很多,,别人都会,自己不会很吃亏啊,,,于是从基础开始一点一点的补ing

    背包问题

    背包问题是动态规划的经典也是基础,,,下面的东西部分来自 背包九讲

    01背包

    01背包指的是对于任意的物品只有 取或不取 两种状态,,

    状态转移方程

    状态转移方程为:

    (F[i,j]=max(F[i-1,j], F[i-1,j-c_i]+w_i))

    外层循环枚举物品总数:(for i=1 to n)

    内层循环枚举背包的容量: (for j=c_i to v)


    空间优化后的状态转移方程:

    (F[j]=max(F[j], F[j-c_i]+w_i))

    外层循环不变,内层循环变为: (for j=v to c_i)

    外层循环可以继续优化为: (for j to max(v-sum_i^nw_i, c_i))

    初始化

    • 恰好装满背包:(F[0]=0,F[1..v]=-infty)
    • 不必装满: (F[0..v]=0)

    初始化F数组就是在没有任何物品可以放入背包时的合法状态,所以,前者只有容量为零的背包什么都不装的情况下是恰好装满的,其他容量的背包都是未定义的状态,无合法解;后者因为不必装满,所以什么都不装的时候就是一个合法解,这时的价值为零。

    例题

    hud-2602

    裸的01背包,,直接做,,,注意判断当前物品是否能放入背包,,再选择放与不放,,

    还有内层循环容量的遍历是从0开始

    memset(dp, 0, sizeof dp);
            for(int i = 1; i <= n; ++i)
                for(int j = 0; j <= v; ++j)
                    if(c[i] <= j)//能放入时,选择放与不放
                        dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - c[i]] + w[i]);
                    else
                        dp[i][j] = dp[i - 1][j];
            printf("%d
    ", dp[n][v]);
    

    空间优化后的方法:

    memset(dp, 0, sizeof dp);
            for(int i = 1; i <= n; ++i)
                for(int j = v; j >= 0; --j)
                    if(c[i] <= j)//能放入时,选择放与不放
                        dp[j] = max(dp[j], dp[j - c[i]] + w[i]);
            printf("%d
    ", dp[v]);
    

    hdu-2546

    题意是:一个总钱数为m的钱包,在剩余金额大于等于5的情况下可以购买任何东西,即使买了一个东西后剩余钱数为负,然后给你这n个东西的标价,每种东西只能购买一次,,

    这道题按01背包做的话,可以将钱包m看成背包的容量,n道菜就是n种物品, 每种物品的价值和花费都是其菜价,,

    这是其中一个点,还有为了尽可能的是利益最大,,我们可以先保留5块,为了最后买那个最贵的菜,,对剩下的n-1个菜选择出价值最大的,,,这样就将这道题转化成了容量为m-5的背包选择一些物品使得总价值最大,,,最后的答案在算上那个最贵的菜就行了,,,

    int dp[maxn], c[maxn], w[maxn];
    int main()
    {
    //    freopen("233.in" , "r" , stdin);
    //    freopen("233.out" , "w" , stdout);
    //    ios_base::sync_with_stdio(0);
    //    cin.tie(0);cout.tie(0);
        int n;
        while(scanf("%d", &n) && n)
        {
            for(int i = 1; i <= n; ++i)
                scanf("%d", &c[i]);
    
            int m;scanf("%d", &m);
    
            if(m < 5)
            {
                printf("%d
    ", m);
                continue;
            }
            m -= 5;
            sort(c + 1, c + 1 + n);
            memset(dp, 0, sizeof dp);
            for(int i = 1; i <= n - 1; ++i)
                for(int j = m; j >= c[i]; --j)
                    dp[j] = max(dp[j], dp[j - c[i]] + c[i]);
            printf("%d
    ", m + 5 - dp[m] - c[n]);
        }
        return 0;
    }
    

    hdu-1171

    题意是:有一些设施,每个设施的价值为 (w_i),,然后要分成两堆,这两堆的价值要尽可能的相近

    显然分后的价值和 (sum) 就是原来的价值和,,然后肯定一个大于等于均值,一个小于等于,,,所以可以将这道题目看成01背包的模型:一个容量为 (sum/2) 的背包,选择装一些物品,这些物品的价值的和费用相同,,求最大的价值

    int dp[maxn], c[maxn], w[maxn];
    int main()
    {
    //    freopen("233.in" , "r" , stdin);
    //    freopen("233.out" , "w" , stdout);
    //    ios_base::sync_with_stdio(0);
    //    cin.tie(0);cout.tie(0);
        int n;
        while(scanf("%d", &n) && n > 0)
        {
            int tot = 0;
            for(int i = 1; i <= n; ++i)
            {
                int a, b;
                scanf("%d%d", &a, &b);
                while(b--)w[++tot] = a;
            }
            memset(dp, 0, sizeof dp);
            int sum = 0;
            for(int i = 1; i <= tot; ++i)sum += w[i];
            for(int i = 1; i <= tot; ++i)
                for(int j = sum / 2; j >= w[i]; --j)
                    dp[j] = max(dp[j], dp[j - w[i]] + w[i]);
            printf("%d %d
    ", sum - dp[sum / 2], dp[sum / 2]);
        }
        return 0;
    }
    

    剩下一些其他题,,以后再说

    完全背包

    完全背包就是在01背包的基础上对于物品的限制解除,,物品不再为只能取一件,而是无限件(实际也不可能是无限件,每一个物品最多取 (lfloor frac{v}{c_i} floor)),,

    将完全背包转化为01背包后, 状态转移方程和01背包的类似,,只有对背包容量的枚举也就是内层循环中,完全背包是递增的顺序而01背包的是递减的顺序,,

    (for j=c_i to v)

    0-1背包和完全背包的不同:

    从二维数组上区别0-1背包和完全背包也就是状态转移方程就差别在放第i中物品时,完全背包在选择放这个物品时,最优解是F[i][j-c[i]]+w[i]即画表格中同行的那一个,而0-1背包比较的是F[i-1][j-c[i]]+w[i],上一行的那一个。

    从一维数组上区别0-1背包和完全背包差别就在循环顺序上,0-1背包必须逆序,因为这样保证了不会重复选择已经选择的物品,而完全背包是顺序,顺序会覆盖以前的状态,所以存在选择多次的情况,也符合完全背包的题意。状态转移方程都为F[i] = max(F[i],dp[F-c[i]]+v[i])。

    例题

    hdu-1114

    题意是:给你一个存钱罐的总质量个单纯存钱罐的质量(也就是差为钱的质量),,以及n种硬币的面值和质量,然后问你最小的金额是多少

    差值可以看作背包的容量,每个硬币的质量为物品的代价,面值为其价值,,然后求最小的价值转移方程里就为min,,初始化再改变一下,,

    int dp[maxn], c[maxn], w[maxn];
    int main()
    {
    //    freopen("233.in" , "r" , stdin);
    //    freopen("233.out" , "w" , stdout);
    //    ios_base::sync_with_stdio(0);
    //    cin.tie(0);cout.tie(0);
        int t;scanf("%d", &t);
        while(t--)
        {
            int e, f;scanf("%d%d", &e, &f);
            int v = f - e;
            int k;scanf("%d", &k);
            for(int i = 1; i <= k; ++i)scanf("%d%d", &w[i], &c[i]);
            memset(dp, inf, sizeof dp);
            dp[0] = 0;
            for(int i = 1; i <= k; ++i)
                for(int j = c[i]; j <= v; ++j)
                    dp[j] = min(dp[j], dp[j - c[i]] + w[i]);
            if(dp[v] >= inf)
                printf("This is impossible.
    ");
            else
                printf("The minimum amount of money in the piggy-bank is %d.
    ", dp[v]);
        }
        return 0;
    }
    

    多重背包

    多重背包就是完全背包的限制版,,每一种物品不再是无限个,,而是给定的个数,最后还是求背包的最大价值什么的,,,

    转化成01背包问题就是对于每一种物品取 (1, 2, 2^2, 2^3,,,2^{k-1},M_i-2^k+1)件,,

    一般的多重背包模板:

    int dp[maxn], c[maxn], w[maxn], num[maxn];
    int n, m, v;//n为物品总数,v为背包容量
    //01背包,该物品的代价,价值
    void ZeroOnePack(int C, int W)
    {
        for(int i = v; i >= C; --i)
            dp[i] = max(dp[i], dp[i - C] + W);
        return;
    }
    //完全背包,该物品的代价,价值
    void CompletePack(int C, int W)
    {
        for(int i = C; i <= v; ++i)
            dp[i] = max(dp[i], dp[i - C] + W);
        return;
    }
    //一次多重背包,该物品的代价,价值,数量
    void OneMuitPack(int C, int W, int M)
    {
        if(v <= C * M)//物品足够多时用完全背包
        {
            CompletePack(C, W);
            return;
        }
        else        //否则用二进制划分成若干件01背包的物品
        {
            int k = 1;
            while(k < M)
            {
                ZeroOnePack(k * C, k * W);//某一个划分成01背包的物品
                M -= k;
                k <<= 1;
            }
            ZeroOnePack(C * M, W * M);//剩下的一些物品
        }
        return;
    }
    

    例题

    hdu-2844

    题意是:n种面值的硬币,每种硬币的个数限定,问你能够组成几种面值和不超过m的组成方法,

    转化成背包问题就是,一个容量为m的背包装一些价值和代价都为面值的物品,其中物品的个数有限制,,问背包内的价值的可能种类

    //cf
    //#include <bits/stdc++.h>
    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <string.h>
    #include <algorithm>
    #define aaa cout<<233<<endl;
    #define endl '
    '
    #define pb push_back
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int, ull> pii;
    const int inf = 0x3f3f3f3f;//1061109567
    const ll linf = 0x3f3f3f3f3f3f3f;
    const double eps = 1e-6;
    const double pi = 3.14159265358979;
    const int maxn = 1e5 + 5;
    const int maxm = 2e5 + 5;
    const ll mod = 1e9 + 7;
    int dp[maxn], c[maxn], w[maxn], num[maxn];
    int n, m, v;//n为物品总数,v为背包容量
    //01背包,该物品的代价,价值
    void ZeroOnePack(int C, int W)
    {
        for(int i = v; i >= C; --i)
            dp[i] = max(dp[i], dp[i - C] + W);
        return;
    }
    //完全背包,该物品的代价,价值
    void CompletePack(int C, int W)
    {
        for(int i = C; i <= v; ++i)
            dp[i] = max(dp[i], dp[i - C] + W);
        return;
    }
    //一次多重背包,该物品的代价,价值,数量
    void OneMuitPack(int C, int W, int M)
    {
        if(v <= C * M)//物品足够多时用完全背包
        {
            CompletePack(C, W);
            return;
        }
        else        //否则用二进制划分成若干件01背包的物品
        {
            int k = 1;
            while(k < M)
            {
                ZeroOnePack(k * C, k * W);//某一个划分成01背包的物品
                M -= k;
                k <<= 1;
            }
            ZeroOnePack(C * M, W * M);//剩下的一些物品
        }
        return;
    }
    int main()
    {
    //    freopen("233.in" , "r" , stdin);
    //    freopen("233.out" , "w" , stdout);
    //    ios_base::sync_with_stdio(0);
    //    cin.tie(0);cout.tie(0);
        while(scanf("%d%d", &n, &m) && n + m)
        {
            for(int i = 1; i <= n; ++i)scanf("%d", &w[i]);
            for(int i = 1; i <= n; ++i)scanf("%d", &num[i]);
            memset(dp, 0, sizeof dp);
            v = m;
            for(int i = 1; i <= n; ++i)
                OneMuitPack(w[i], w[i], num[i]);
            int ans = 0;
            for(int i = 1; i <= m; ++i)if(dp[i] == i)++ans;
            printf("%d
    ", ans);
        }
        return 0;
    }
    

    混合背包

    混合背包就是n种物品有的只能取一次,有的能取有限次,有的能取无限次,然后问你对于容量为v的背包的可取最大价值是多少

    直接判断每个物品的种类,使用不同的背包类型就行了

    例题

    codevs-3269

    题意就是混合背包的定义,,直接做就行

    //cf
    //#include <bits/stdc++.h>
    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <string.h>
    #include <algorithm>
    #define aaa cout<<233<<endl;
    #define endl '
    '
    #define pb push_back
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int, ull> pii;
    const int inf = 0x3f3f3f3f;//1061109567
    const ll linf = 0x3f3f3f3f3f3f3f;
    const double eps = 1e-6;
    const double pi = 3.14159265358979;
    const int maxn = 2e5 + 5;
    const int maxm = 2e5 + 5;
    const ll mod = 1e9 + 7;
    int dp[maxn], c[maxn], w[maxn], num[maxn];
    int n, m, v;//n为物品总数,v为背包容量
    //01背包,该物品的代价,价值
    void ZeroOnePack(int C, int W)
    {
        for(int i = v; i >= C; --i)
            dp[i] = max(dp[i], dp[i - C] + W);
        return;
    }
    //完全背包,该物品的代价,价值
    void CompletePack(int C, int W)
    {
        for(int i = C; i <= v; ++i)
            dp[i] = max(dp[i], dp[i - C] + W);
        return;
    }
    //一次多重背包,该物品的代价,价值,数量
    void OneMuitPack(int C, int W, int M)
    {
        if(v <= C * M)//物品足够多时用完全背包
        {
            CompletePack(C, W);
            return;
        }
        else        //否则用二进制划分成若干件01背包的物品
        {
            int k = 1;
            while(k < M)
            {
                ZeroOnePack(k * C, k * W);//某一个划分成01背包的物品
                M -= k;
                k <<= 1;
            }
            ZeroOnePack(C * M, W * M);//剩下的一些物品
        }
        return;
    }
    int main()
    {
    //    ios_base::sync_with_stdio(0);
    //    cin.tie(0);cout.tie(0);
        scanf("%d%d", &n, &v);
        for(int i = 1; i <= n; ++i)scanf("%d%d%d", &c[i], &w[i], &num[i]);
        memset(dp, 0, sizeof dp);
        for(int i = 1; i <= n; ++i)
        {
            if(num[i] == 1)
                ZeroOnePack(c[i], w[i]);
            else if(num[i] == -1)
                CompletePack(c[i], w[i]);
            else
                OneMuitPack(c[i], w[i], num[i]);
        }
        printf("%d
    ", dp[v]);
        return 0;
    }
    

    二维费用背包

    二维费用指的就是相比之前的背包问题侑多了一个费用的影响因素,,对于一个物品有两个不同的代价以及其容量,,做法和前面的一样,dp数组增加一维就行了,,

    例题

    hdu-2159

    转化成背包问题就是代价一是忍耐度,背包容量为m;代价二就是打怪,容量就是s,,求最大的价值(经验值)与n的大小关系,,,

    //cf
    //#include <bits/stdc++.h>
    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <string.h>
    #include <algorithm>
    #define aaa cout<<233<<endl;
    #define endl '
    '
    #define pb push_back
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int, ull> pii;
    const int inf = 0x3f3f3f3f;//1061109567
    const ll linf = 0x3f3f3f3f3f3f3f;
    const double eps = 1e-6;
    const double pi = 3.14159265358979;
    const int maxn = 1e3 + 5;
    const int maxm = 2e5 + 5;
    const ll mod = 1e9 + 7;
    int dp[maxn][maxn], c[maxn], w[maxn], num[maxn];
    int n, m, v;//n为物品总数,v为背包容量
    
    int main()
    {
    //    freopen("233.in" , "r" , stdin);
    //    freopen("233.out" , "w" , stdout);
    //    ios_base::sync_with_stdio(0);
    //    cin.tie(0);cout.tie(0);
        int n, m, k, s;
        while(~scanf("%d%d%d%d", &n, &m, &k, &s))
        {
            memset(w, 0, sizeof w);
            memset(c, 0, sizeof c);
            memset(dp, 0, sizeof dp);
            for(int i = 1; i <= k; ++i)
                scanf("%d%d", &w[i], &c[i]);
            int ans = inf;
            for(int i = 1; i <= k; ++i)
                for(int j = c[i]; j <= m; ++j)
                    for(int k = 1; k <= s; ++k)
                    {
                        dp[j][k] = max(dp[j][k], dp[j - c[i]][k - 1] + w[i]);
                        if(dp[j][k] >= n)ans = min(ans, j);
                    }
            if(ans > m)printf("-1
    ");
            else       printf("%d
    ", m - ans);
        }
        return 0;
    }
    

    (loading)

    分组背包

    分组背包就是:一堆物品被划分成了K组,同一组的物品只能选择一个,或者这组不选,其他的条件和其他背包模型一样,,

    解决方法,再加一层对每组背包的枚举

    伪代码:

    (for k=1 to K)

    (for v=V to V)

    (for item i in group k)

    (F[v]=max(F[v], F[v-C_i]+W_i))

    例题

    hdu-1712

    题意就是有n节课,每一课上几天的价值给你,,一共要上m节课,问最大的价值,,

    把这道题看成容量为m的背包,装分为n组的物品最大的价值就行

    int dp[maxn];
    int main()
    {
        int n, m;
        int a[maxn][maxn];
        while(scanf("%d%d", &n, &m) && n + m)
        {
            memset(a, 0, sizeof a);
            for(int i = 1; i <= n; ++i)
                for(int j = 1; j <= m; ++j)
                    scanf("%d", &a[i][j]);
            memset(dp, 0, sizeof dp);
            for(int k = 1; k <= n; ++k)//枚举组数
                for(int j = m; j >= 0; --j)//枚举背包的容量
                    for(int i = 1; i <= m; ++i)//枚举第k组的物品
                        if(i <= j)//保证能装下
                        dp[j] = max(dp[j], dp[j - i] + a[k][i]);
            printf("%d
    ", dp[m]);
        }
        return 0;
    }
    

    hdu-3033

    题意就是一堆鞋子,某一些是一个牌子的,然后每一双鞋有一个价格(看作代价),一个价值,每个牌子至少取一双,问最大的价值,,,

    与上一道不同的是每一组的物品不再是最多选一个了,,一组可以选多个,每一组都要选一个,,

    dp[i][j]表示的是前i组在容量为j的背包所取的最大价值,,当前状态dp[i][j]可以由 前一状态在本组选一个物品 推来,也可以由 当前状态在本组再取一个物品 推来,,

    初始化也不同了,,除了那一组都不选的那一行dp为零,,其他都为负,即未定义状态,,由这个判断是否有解,,

    参考1
    参考2
    参考3

    //hdu
    //#include <bits/stdc++.h>
    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <string.h>
    #include <algorithm>
    #define aaa cout<<233<<endl;
    #define endl '
    '
    #define pb push_back
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int, int> pii;
    const int inf = 0x3f3f3f3f;//1061109567
    const ll linf = 0x3f3f3f3f3f3f3f;
    const double eps = 1e-6;
    const double pi = 3.14159265358979;
    const int maxn = 1e4 + 5;
    const int maxm = 2e5 + 5;
    const ll mod = 1e9 + 7;
    int dp[11][maxn];
    pii a[11][maxn];
    int num[11];
    int main()
    {
    //    freopen("233.in" , "r" , stdin);
    //    freopen("233.out" , "w" , stdout);
    //    ios_base::sync_with_stdio(0);
    //    cin.tie(0);cout.tie(0);
        int n, m, K;
        while(~scanf("%d%d%d", &n, &m, &K))
        {
            memset(num, 0, sizeof num);
            for(int i = 1; i <= n; ++i)
            {
                int aa, bb, cc;
                scanf("%d%d%d", &aa, &bb, &cc);
                ++num[aa];
                a[aa][num[aa]].first = bb;
                a[aa][num[aa]].second = cc;
            }
            memset(dp, -1, sizeof dp);
            //for(int i = 0; i <= m; ++i)dp[0][i] = 0;
            memset(dp[0], 0, sizeof dp[0]);
            //不能写成memset(dp[0], 0, sizeof dp);
            for(int k = 1; k <= K; ++k)
                for(int i = 1; i <= num[k]; ++i)
                    for(int j = m; j >= a[k][i].first; --j)
                    {
                        if(dp[k][j - a[k][i].first] >= 0)
                            dp[k][j] = max(dp[k][j], dp[k][j - a[k][i].first] + a[k][i].second);
                        if(dp[k - 1][j - a[k][i].first] >= 0)
                            dp[k][j] = max(dp[k - 1][j - a[k][i].first] + a[k][i].second, dp[k][j]);
                    }
            if(dp[K][m] < 0)printf("Impossible
    ");
            else            printf("%d
    ", dp[K][m]);
        }
        return 0;
    }
    

    这道题没怎么理解还,,
    (loading)

    剩下一些其他的内容,暂时先放放,,

  • 相关阅读:
    js call
    正则简单学习
    第10天:apply和call、bind、函数作为参数、返回值使用、闭包、递归的样例
    第9天:原型、继承、函数使用推荐以及this的指向
    Identity Server introspect 调用 /connect/introspect
    windows forget jenkins password.
    转载(Asp.net Core 中试使用ZKWeb.System.Drawing)
    EFCore & Mysql migration on Production
    HTML to PDF pechkin
    Log4net
  • 原文地址:https://www.cnblogs.com/31415926535x/p/10403589.html
Copyright © 2011-2022 走看看