zoukankan      html  css  js  c++  java
  • dp-背包问题

    背包问题理论模板

    一个背包总容量为V, 现在有N个物品, 第i个物品容量为weight[i], 价值为value[i], 现在往背包里面装东西, 怎样装才能使背包内物品总价值最大.主要分为3类:

    • 01背包, 每个物品只能取0个,或者1个.
    • 完全背包, 每个物品可以取无限次.
    • 多重背包, 每种物品都有个数限制, 第i个物品最多可以为num[i]个.

    总体的,又分为背包刚好装满,与背包没有装满两种情况

    01背包问题

    每种物品都只有1个,只有选择与不选择两种状态
    有N件物品和一个容量为V的背包,每种物品只有一件,可以选择放或不放。第i件物品的重量是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的重量总和不超过背包容量,且价值总和最大。

    对于任何只存在两种状态的问题都可以转化为01背包问题

    定义状态dp[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。
    状态转移方程:
    dp[i][v]=max(dp[i-1][v], dp[i-1][v-w[i]]+v[i])

    `dp[v]=max(dp[v],dp[v-w[i]]+v[i])`
    

    空间复杂度O(NW)或者O(W),时间复杂度为O(nW)

    for(int i=0;i<n;i++){
        for(int j=背包大小;j>=w[i];j--){
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
        }
    }
    

    分析:
    首先,大循环从0到n是指n个物体
    其次,内循环是从背包大小到当前物体的重量,是为了减少时间复杂度而这么弄的
    对于w[i]是物体的重量,如果剩余背包的大小小于w[i]了,就无需再循环,所以一个端点在w[i]
    而j的含义是当前的背包总容量,而容量不可能大于背包大小,所以另一个大小就在背包大小
    如果是升序,那么就会对dp[j-w[i]]进行操作,也就意味着,如果j是w[i]的倍数,那么v[i]就会不断地加进去,也就是完全背包问题了
    而对于降序的话,也就保证了每次第j个物品都只能被放入1此,而不是多次,就是01背包了

    多重背包问题

    每种物品最多有n件可用
    有N种物品和一个容量为V的背包。第i种物品最多有n件可用,每件体积是c,价值是w。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。

    对于多重背包问题,可以转化为01背包问题,比如2个价值为5,重量为2的物品,可以转化为a和b两个物品,一个价值为5,重量为2,一个价值也为5,重量也为2

    状态转移方程:
    dp[i][j]=max(dp[i-1][j],dp[i-1][j-k*w[i]]+k*v[i]) 其中0<=k<=c[i]

    `dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i])`
    

    k是每种物品放的数量

     for(int i=1;i<=n;i++)
        for(int j=m;j>=0;j--)
            for(int k=1;k<=c[i];k++){
                dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
            }
        }
    }
    

    完全背包问题

    每种物品都有无限件
    有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的体积是c,价值是w。将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。

    状态转移方程:
    dp[i][j]=max(dp[i][j],dp[i-1][v-k*w[i]]+k*v[i]) 其中0<=k*w[i]<=背包大小

    `dp[j] = max(dp[j], dp[j - w[i]] + v[i]);`
    

    分析见01背包物体,和01背包一样,就是内循环的循环方向不同而已,一个升,一个降

    空间杂度为O(NW)或O(W),时间复杂度为O(NW)

    代码

    for (int i = 1; i <= n; i++) {
        for (int j = w[i]; j <= 背包大小; j++) {
            dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
        }
    }
    

    背包混用

    将3种背包进行混用,如果对于有些物品最多只能选一次,有些可以无限选
    则利用01背包和完全背包的一行代码不同,进行判断

    for(int i=0;i<n;i++){
        if(第i个物品是01背包){
            for(int j=背包大小;j>=w[i];j--){
                dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
            }
        }else if(第i个物品是完全背包){
            for (int j = w[i]; j <= 背包大小; j++) {
                dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
            }
        }
    }
    

    如果再加上多重背包的话
    背包混用伪代码

    for i= 1 to n
        if 第i件物品属于01背包
            ZeroOnePack(dp,wi,vi)
        else if 第i件物品属于完全背包
            CompletePack(dp,wi,vi)
        else if 第i件物品属于多重背包
            MultiplePack(dp,wi,vi,ni)
    

    总结模板

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    const int N=1000000;
    int v[N],w[N];
    int dp[N];
    void ZeroOnePack(int i){
        for(int j=背包大小;j>=w[i];j--){
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
        }
    }
    void CompletePack(int i){
        for(int j=w[i];j<=背包大小;j++){
            dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
        }
    }
    void MultiplePack(int i){
        for(int j=m;j>=0;j--)
            for(int k=1;k<=c[i];k++){
                dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
            }
        }
    }
    int main(){
        memste(dp,0,sizeof(dp));
        for(int i=0;i<n;i++){
        if(第i个物品是01背包问题){
            ZeroOnePack(i);
        }else if(第i个物品是完全背包问题){
            CompletePack(i);
        }else if(第i个物品是多重背包问题){
            MultiplePack(i);
        }
    }
    

    做题记录

    第一题:

    完全背包问题
    HUD1248

    Problem Description
    不死族的巫妖王发工资拉,死亡骑士拿到一张N元的钞票(记住,只有一张钞票),为了防止自己在战斗中频繁的死掉,他决定给自己买一些道具,于是他来到了地精商店前.

    死亡骑士:"我要买道具!"

    地精商人:"我们这里有三种道具,血瓶150块一个,魔法药200块一个,无敌药水350块一个."

    死亡骑士:"好的,给我一个血瓶."

    说完他掏出那张N元的大钞递给地精商人.

    地精商人:"我忘了提醒你了,我们这里没有找客人钱的习惯的,多的钱我们都当小费收了的,嘿嘿."

    死亡骑士:"......"

    死亡骑士想,与其把钱当小费送个他还不如自己多买一点道具,反正以后都要买的,早点买了放在家里也好,但是要尽量少让他赚小费.

    现在死亡骑士希望你能帮他计算一下,最少他要给地精商人多少小费.

    Input
    输入数据的第一行是一个整数T(1<=T<=100),代表测试数据的数量.然后是T行测试数据,每个测试数据只包含一个正整数N(1<=N<=10000),N代表死亡骑士手中钞票的面值.

    注意:地精商店只有题中描述的三种道具.

    Output
    对于每组测试数据,请你输出死亡骑士最少要浪费多少钱给地精商人作为小费.

    Sample Input

    2
    900
    250

    Sample Output

    0
    50

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    int w[]={150,200,350};
    int v[]={150,200,350};
    int dp[10005];
    int main(){
        int t;
        cin>>t;
        while(t--){
            int n;
            scanf("%d",&n);
            memset(dp,0,sizeof(dp));
    
            for(int i=0;i<3;i++){
                for(int j=w[i];j<=n;j++){
                    dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
                }
            }
            printf("%d
    ",n-dp[n]);
        }
    
        return 0;
    }
    

    第二题:

    完全背包问题
    Problem Description:
    Before ACM can do anything, a budget must be prepared and the necessary financial support obtained. The main income for this action comes from Irreversibly Bound Money (IBM). The idea behind is simple. Whenever some ACM member has any small money, he takes all the coins and throws them into a piggy-bank. You know that this process is irreversible, the coins cannot be removed without breaking the pig. After a sufficiently long time, there should be enough cash in the piggy-bank to pay everything that needs to be paid.

    But there is a big problem with piggy-banks. It is not possible to determine how much money is inside. So we might break the pig into pieces only to find out that there is not enough money. Clearly, we want to avoid this unpleasant situation. The only possibility is to weigh the piggy-bank and try to guess how many coins are inside. Assume that we are able to determine the weight of the pig exactly and that we know the weights of all coins of a given currency. Then there is some minimum amount of money in the piggy-bank that we can guarantee. Your task is to find out this worst case and determine the minimum amount of cash inside the piggy-bank. We need your help. No more prematurely broken pigs!
    Input
    The input consists of T test cases. The number of them (T) is given on the first line of the input file. Each test case begins with a line containing two integers E and F. They indicate the weight of an empty pig and of the pig filled with coins. Both weights are given in grams. No pig will weigh more than 10 kg, that means 1 <= E <= F <= 10000. On the second line of each test case, there is an integer number N (1 <= N <= 500) that gives the number of various coins used in the given currency. Following this are exactly N lines, each specifying one coin type. These lines contain two integers each, Pand W (1 <= P <= 50000, 1 <= W <=10000). P is the value of the coin in monetary units, W is it's weight in grams.
    Output
    Print exactly one line of output for each test case. The line must contain the sentence "The minimum amount of money in the piggy-bank is X." where X is the minimum amount of money that can be achieved using coins with the given total weight. If the weight cannot be reached exactly, print a line "This is impossible.".
    Sample Input

    3
    10 110
    2
    1 1
    30 50

    10 110
    2
    1 1
    50 30

    1 6
    2
    10 3
    20 4

    Sample Output

    The minimum amount of money in the piggy-bank is 60.
    The minimum amount of money in the piggy-bank is 100.
    This is impossible.

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    const long long INF=500005,N=10005;
    int w[N];
    int v[N];
    int dp[N];
    int main(){
        int t;
        cin>>t;
        while(t--){
            int e,f;
            scanf("%d%d",&e,&f);
            int n;
            cin>>n;
            int W=f-e;
            for(int i=0;i<n;i++){
                scanf("%d%d",&v[i],&w[i]);
            }
            //求最小值,所以对所有的dp进行赋值大数字,但除了第一个数,因为对所有赋值的话,最小值就是大数
            for(int i=1;i<=W;i++){
                dp[i]=INF;
            }
    
            for(int i=0;i<n;i++){
                for(int j=w[i];j<=W;j++){
                    dp[j]=min(dp[j],dp[j-w[i]]+v[i]);
                }
            }
    
            if(dp[W]==INF){
                printf("This is impossible.
    ");
            }else{
                printf("The minimum amount of money in the piggy-bank is %d.
    ",dp[W]);
            }
    
        }
    
        return 0;
    }
    
    

    第三题:

    概率dp的01背包问题
    HDU1203
    Problem Description
    Speakless很早就想出国,现在他已经考完了所有需要的考试,准备了所有要准备的材料,于是,便需要去申请学校了。要申请国外的任何大学,你都要交纳一定的申请费用,这可是很惊人的。Speakless没有多少钱,总共只攒了n万美元。他将在m个学校中选择若干的(当然要在他的经济承受范围内)。每个学校都有不同的申请费用a(万美元),并且Speakless估计了他得到这个学校offer的可能性b。不同学校之间是否得到offer不会互相影响。“I NEED A OFFER”,他大叫一声。帮帮这个可怜的人吧,帮助他计算一下,他可以收到至少一份offer的最大概率。(如果Speakless选择了多个学校,得到任意一个学校的offer都可以)。

    Input
    输入有若干组数据,每组数据的第一行有两个正整数n,m(0<=n<=10000,0<=m<=10000)
    后面的m行,每行都有两个数据ai(整型),bi(实型)分别表示第i个学校的申请费用和可能拿到offer的概率。
    输入的最后有两个0。

    Output
    每组数据都对应一个输出,表示Speakless可能得到至少一份offer的最大概率。用百分数表示,精确到小数点后一位。

    Sample Input

    10 3
    4 0.1
    4 0.2
    5 0.3
    0 0

    Sample Output

    44.0%

    Hint

    You should use printf("%%") to print a '%'.

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    const int N=100000+5;
    int v[N];
    float p[N],dp[N];
    int main(){
        int n,m;//背包大小和学校数目
        while(scanf("%d%d",&n,&m),(n+m)!=0){
            for(int i=0;i<m;i++){
                scanf("%d%f",&v[i],&p[i]);
            }
    
            memset(dp,0,sizeof(dp));
    
            for(int i=0;i<m;i++){
                for(int j=n;j>=v[i];j--){
                    dp[j]=max(dp[j],1-(1-dp[j-v[i]])*(1-p[i]));
                }
            }
    
    
            printf("%.1f%%
    ",dp[n]*100);
    
        }
        return 0;
    }
    

    第四题: 多重背包问题

    多重背包问题HDU2191,此方法时间复杂度为O(NCV)

    Problem Description
    现在假设你一共有资金n元,而市场有m种大米,每种大米都是袋装产品,其价格不等,并且只能整袋购买。
    请问:你用有限的资金最多能采购多少公斤粮食呢?

    Input
    输入数据首先包含一个正整数C,表示有C组测试用例,每组测试用例的第一行是两个整数n和m(1<=n<=100, 1<=m<=100),分别表示经费的金额和大米的种类,然后是m行数据,每行包含3个数p,h和c(1<=p<=20,1<=h<=200,1<=c<=20),分别表示每袋的价格、每袋的重量以及对应种类大米的袋数。

    Output
    对于每组测试数据,请输出能够购买大米的最多重量,你可以假设经费买不光所有的大米,并且经费你可以不用完。每个实例的输出占一行。

    Sample Input
    1
    8 2
    2 100 4
    4 100 2

    Sample Output
    400

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    int main(){
        int t;
        cin>>t;
        while(t--){
    
            int n,m;
            scanf("%d%d",&n,&m);
    
            int w[105],v[105],c[105];
            int dp[205];
    
            memset(dp,0,sizeof(dp));
    
            for(int i=0;i<m;i++){
                scanf("%d%d%d",&v[i],&w[i],&c[i]);
            }
    
            for(int i=0;i<m;i++){
                for(int k=1;k<=c[i];k++){
                    for(int j=n;j>=v[i];j--){
                       dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
                    }
                }
            }
            printf("%d
    ",dp[n]);
    
        }
    
    
        return 0;
    }
    

    优化

    二进制优化时间不会。。。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    int n,m;
    int w[105],v[105],c[105];
    int dp[205];//dp[i]指的是购买价值为i的大米时的总重量
    
    //v为当前的价值,w为当前的重量
    void zeroonebag(int v,int w){
        for(int j=n;j>=v;j--){
            dp[j]=max(dp[j],dp[j-v]+w);
        }
    }
    void completebag(int v,int w){
        for(int j=v;j<=n;j++){
            dp[j]=max(dp[j],dp[j-v]+w);
        }
    }
    void multiple(int v,int w,int c){
        //价值大于拥有的价值,则是完全背包问题
        if(c*v>=n){
            completebag(v,w);
            return;
        }
        
        //01背包
        int k=1;
    
        while(k<=c){
            zeroonebag(k*v,k*w);
            c=c-k;
            k=k*2;
        }
    
        zeroonebag(c*v,c*w);
    
    }
    int main(){
        int t;
        cin>>t;
        while(t--){
    
    
            scanf("%d%d",&n,&m);
    
            memset(dp,0,sizeof(dp));
    
            for(int i=0;i<m;i++){
                scanf("%d%d%d",&v[i],&w[i],&c[i]);
            }
    
    
            for(int i=0;i<m;i++){
                multiple(v[i],w[i],c[i]);
            }
    
            printf("%d
    ",dp[n]);
    
    
        }
    
        return 0;
    }
    
  • 相关阅读:
    “ODBC驱动程序不支持动态记录集”错误的解决
    Pro *C/C++学习笔记(一)
    探讨全局变量的析构顺序
    指针和数组关系初探
    (转)Visual C++开发工具与调试技巧整理
    对利用Session纪录datagrid模板列中CheckBox的状态的一点改进
    大学老师列传
    重读保尔的意义
    Rich Edit控件的使用
    C++程序员常用工具集
  • 原文地址:https://www.cnblogs.com/Emcikem/p/11333818.html
Copyright © 2011-2022 走看看