zoukankan      html  css  js  c++  java
  • 动态规划——背包问题入门

    动态规划——背包问题入门


    1. 01背包


    概述

    给出N个物品,每个物体都具有一定的体积和价值,而每个物体都只有一个。 拥有一个V体积的背包,问应如何装包才能使背包中物体的总价值最大?

    解题思路

    背包问题是典型的动态规划类题目,而动态规划是典型的通过规律找出正解的方法。所以解题思路的关键在于如何寻找不同数据之间的关系(状态转移)。

    直接描述不方便解释,我们以例题为例:

    (注:01背包问题不涉及因为物体的形状、大小等而如何放入背包的问题,只是单纯考虑体积和价值。我们可以理解成每个物体都是液体,而这个背包是个大水缸(当然,每种液体要么全倒进去,要么一点不倒,不允许倒一部分。))


    例:HDU2602 Bone Collector

    Problem

    Many years ago , in Teddy’s hometown there was a man who was called “Bone Collector”. This man like to collect varies of bones , such as dog’s , cow’s , also he went to the grave …
    The bone collector had a big bag with a volume of V ,and along his trip of collecting there are a lot of bones , obviously , different bone has different value and different volume, now given the each bone’s value along his trip , can you calculate out the maximum of the total value the bone collector can get ?

    简单释义

    很久以前,在Teddy的家乡有一个被称作 “骨头收集者” 的人。这个人喜欢收集各式各样的骨头 —— 狗的,牛的,当然他也经常会去墓地转转......(是个狼灭)
    这个人有一个总容量为V的背包,而在他的旅途中他有很多骨头要收集。而很明显,不同的骨头有着不同的体积和不同的价值。现在给定他找到的每个骨头都是什么样的,请计算这人能装进包里、收集到的骨头的最大价值是多少?

    Input

    The first line contain a integer T , the number of cases.
    Followed by T cases , each case three lines , the first line contain two integer N , V, (N <= 1000 , V <= 1000 )representing the number of bones and the volume of his bag. And the second line contain N integers representing the value of each bone. The third line contain N integers representing the volume of each bone.

    输入格式

    输入第一行包含一个整数T,代表不同情况的数量(多组数据)。
    跟随的T种情况一个情况有三行,第一行包含两个整数N、V(N<=1000,V<=1000)代表骨头的数量和他背包的容量。第二行包含N个整数代表每块骨头的价值。第三行包含N个整数代表每块骨头的体积。

    Output

    One integer per line representing the maximum of the total value (this number will be less than (2^{31})).

    输出格式

    每行一个整数,代表总价值的最大值(这个数字不大于(2^{31}))。

    输入样例

    1
    5 10
    1 2 3 4 5
    5 4 3 2 1

    输出样例

    14

    题目分析

    动态规划,即要求在各种可能的局部的解中,找到那些可能达到最优的解的局部解并找到最优解,而放弃其他的解,以此实现“大事化小,小事化了”。
    以本题来说,这个狼灭装包有成千上万种方法,就有成千上万种局部解,而我们要找到的是价值最大的那一种,而这一种是成千上万种方法中的一种,也就是局部解的一种,而这一种是最优解。

    我们给一组数据:

    1
    3 5
    5 12 20
    1 2 4

    首先我们可以这样保存数据:

    这个狼灭的每个骨头的体积是v[b],价值是w[b],给每个骨头编个号就是b,在前面已经声明好了:

    	int T;
    	scanf("%d",&T);
    	for(int c=1;c<=T;c++)
    	{
    		int N,V;
    		scanf("%d%d",&N,&V);
    		for(int b=1;b<=N;b++)
    			scanf("%d",&w[b]);
    		for(int b=1;b<=N;b++)
    			scanf("%d",&v[b]);
            }
    

    而他选择要如何装包的过程,就是他 做决策 的过程。

    先来看他拿的第一块骨头:
    这块骨头的价值是5,体积是1。
    我们只有这一块骨头,那么无论我们的背包有多大,我们都只能放这一块骨头进去。那么就有这样的关系:

    总价值 1 2 3 4 5 6 7 ...... 背包体积
    1 5 5 5 5 5 5 5 ......
    骨头编号

    现在这个狼灭又捡来了他的第二块骨头:那么他的背包体积要是比3大的话,他就可以装进两块骨头了:

    ——
    他的背包体积为1,他只能放一块骨头1,价值是5,这也是最大价值;
    他的背包体积为2,他可以放一块骨头2,价值是12,是最大价值;
    他的背包体积为3,他可以放一块骨头2和一块骨头1,价值是17,是最大价值;
    他的背包体积为4,他仍然只可以放一块骨头2和一块骨头1,价值是17,价值最大;
    接下来他的背包体积再怎么大,他也只能放一块骨头2和一块骨头1,所以往后价值都是17:

    总价值 1 2 3 4 5 6 7 ...... 背包体积
    1 5 5 5 5 5 5 5 ......
    2 5 12 17 17 17 17 17 ......
    骨头编号

    接下来他搞来了他的第三块骨头:体积4,价值20。

    ——
    他的背包体积为1,他只能放一块骨头1,价值是5,这也是最大价值;
    他的背包体积为2,他可以放一块骨头2,价值是12,是最大价值;
    他的背包体积为3,他可以放一块骨头2和一块骨头1,价值是17,是最大价值,到这和前面都一样。
    他的背包体积为4,他就可以放一块骨头3了,那价值是20,最大了;
    他的背包体积为5,他可以放一块骨头3外加一块骨头1,这是25;
    他的背包体积为6,他可以放一块骨头3外加一块骨头2,这是32;
    他的背包体积为7,他可以放一块骨头3、一块骨头2、一块骨头1,那么就是37;
    再往后的话,他无论如何也只能装这3块骨头了,往后都是37。

    总价值 1 2 3 4 5 6 7 ...... 背包体积
    1 5 5 5 5 5 5 5 ......
    2 5 12 17 17 17 17 17 ......
    3 5 12 17 20 25 32 37 ......
    骨头编号

    ……

    既然有了这么个表,我们便可以把这个表记下来。我们来一个数组:

    int dp[i][j];//在这里,表示考虑到前i块骨头,背包体积为j,可以获得的最大价值。那么dp[i][j]就是这种情况下的最大价值。
    

    那么这个数组就是:

    dp[i][j] 1 2 3 4 5 6 7 ...... j
    1 5 5 5 5 5 5 5 ......
    2 5 12 17 17 17 17 17 ......
    3 5 12 17 20 25 32 37 ......
    i

    乍一看好像没什么规律,而接下来就是关键——在这个表中找出数据的规律,这也就是动态规划的精华:推导状态转移方程 ——也就是这个狼灭做决策的过程。

    然而这个狼灭拿到了一块骨头i,他需要考虑的问题只不过是他要不要把这块骨头i放进包。
    他要是决定不放,那么他决策完了就是:dp[i][j]=dp[i-1][j];
    ——包里东西没变,和[i-1][j]时候的包是一样的;

    他要是决定放,那么他决策完了就是:dp[i][j]=dp[i-1][j-v[i]]+w[i];
    ——他往包里放了一个i号骨头,那么这时候包里就有了这块i号骨头,而体积也被占去了v[i],但价值多了w[i]——于是反过来想,他放进去之后的dp[i][j]——--》就是他没有放i、体积是j-v[i]时候的那个状态加上i的价值《--!!

    (简单的东西拿字母表示出来就有些烧脑了。。。QAQ)

    于是他要是决定不放,那么状态[i][j]就是从[i-1][j]那里转移过来的;
    他要是决定放,那么状态[i][j]就是从[i-1][j-v[i]]那里转移过来的;
    他放不放都行,而我们要找的是价值最大的,所以这个时候的状态就是——

    dp[i][j]=max(dp[i-1][j-v[i]]+w[i],dp[i-1][j]);//Note:j>=v[i];
    

    这就是这道题的状态转移方程,接下来我们只消得把它塞进程序里就OK了:

    #include<cstdio>
    #include<algorithm> //max()的头
    using namespace std;
    int dp[1080][1080];
    int w[1080],v[1080];
    int main()
    {
    	int T;
    	scanf("%d",&T);
    	for(int c=1;c<=T;c++)
    	{
    		int N,V;
    		scanf("%d%d",&N,&V);
    		for(int b=1;b<=N;b++)
    			scanf("%d",&w[b]);
    		for(int b=1;b<=N;b++)
    			scanf("%d",&v[b]);
    		for(int i=1;i<=N;i++)//i个物品
    			for(int j=0;j<=V;j++)//背包体积为j——转两个循环列举各个情况的状态,并用不同的状态进行转移:
    			{
    				if(j>=v[i])//使用状态转移方程
    					dp[i][j]=max(dp[i-1][j-v[i]]+w[i],dp[i-1][j]);
    				if(j<v[i])//如果此时背包的体积比这个物体的体积小,那无论如何也放不进去,所以走“不放”的状态转移
    					dp[i][j]=dp[i-1][j];
    			}
    		printf("%d
    ",dp[N][V]);
    	}
    	return 0;
    }
    

    ……


    可以看出来了:他要决定怎么放,那么他的决策有千万多种多,在这里面找出最优状态当然会累S。
    而我们从他只有1块骨头开始,推导出两块骨头、三块骨头……并找出其中的联系,就可以找到他有N块骨头应该怎么放,那么我们便很容易在不断的转移过程中找出最优解,这也就是动态规划的“大事化小,小事化了”思维的精华所在。


    未完待续。。。 算了还是完了吧。

  • 相关阅读:
    后缀数组
    后缀树
    字典树
    Revit二次开发: 文件损坏
    遍历取出指定文件夹下所有的文件
    Python类、模块、包的区别
    Opencv-python画图基础知识
    JSON C# Class Generator ---由json字符串生成C#实体类的工具
    Handsontable Dropdown with key-value pair
    怎样监听vue.js中v-for全部渲染完成?
  • 原文地址:https://www.cnblogs.com/izwb003/p/dynamic-programming_knapsack-problem.html
Copyright © 2011-2022 走看看