浅析DP之01背包(采药 + 双塔问题)

目录

目录

目录

何为DP

采药

状态定义

代码

双塔问题

状态定义

代码(2)


何为DP

按照网络上的话来说,是这样的:

> 就是把多级最优化问题分解成一系列的单阶问题。在不断增加的过程中,不断的计算当前问题的最优解
>
*其实就是将一个较大的问题,进行剖析,发现其中的关系,列成几个式子,再用编程的语言书写下来*
这就需要我们反复读题,而不是一味地去打代码,也许有的人会说:“光想不打,永远也没有思路。”但往往将读入输出弄完后就只剩下删代码了(我没有思路,换个题做吧)。
 总而言之,做DP题,没有草稿纸和笔是不行的。

采药


 ### 回归原题,状态转移式
 f[i][j]:第 i 个人假如只剩 j 的背包容量了,他所能获得的最大价值
 而对于每一个物品,它只给你两个选择:
 1. 买下这个物品
 2. 再想想,不买这一件物品

状态定义


 所以一些大佬就想出了这个:所以一些大佬就想出了这个:
**f[i][j] = max(f[i - 1][j],f[i - 1][j - v[i]] + c[i]);**
~~很简单吧~~

代码

#include<cstdio>
#define L 100 + 5
#define M 1000 + 5
inline int max(int x,int y){
    return x < y ? y : x;
}
int V,n;
int c[L],v[L],f[L][M];
int main()
{
	scanf("%d%d", &V, &n);
	for (int i = 1;i <= n;i ++ )
		scanf("%d%d", &v[i], &c[i]);
	for (int i = 1;i <= n;i ++ )
		for (int j = V;j >= 0; j-- ){
            if (j >= v[i])
                f[i][j] = max(f[i - 1][j],f[i - 1][j - v[i]] + c[i]);//j是指所剩的容量
            else
                f[i][j] = f[i - 1][j];//这一步不能少,否则当以后的计算只剩下j的容量时,最大价值会出错(0)
		}
	printf("%d",f[n][V]);
	return 0;
}

然后你会猛然发现 i 这东西可要可不要,一个浪费空间的附赠品

变一下

就这样啦

#include<cstdio>
#define L 100 + 5
#define M 1000 + 5
inline int max(int x,int y){
    return x < y ? y : x;
}
int V,n;
int c[L],v[L],f[M];
int main()
{
	scanf("%d%d", &V, &n);
	for (int i = 1;i <= n;i ++ )
		scanf("%d%d", &v[i], &c[i]);
	for (int i = 1;i <= n;i ++ )
		for (int j = V;j >= v[i]; j-- )
                f[j] = max(f[j],f[j - v[i]] + c[i]);//j是指所剩的容量
	printf("%d",f[V]);
	return 0;
}

简单了许多吧,要是你想让你的代码高级一些,又想少费点空间,滚动数组也不失为一个好东西

#include<cstdio>
#define L 100 + 5
#define M 1000 + 5
#define N 2
inline int max(int x,int y){
    return x < y ? y : x;
}
int V,n,k = 1;
int c[L],v[L],f[N][M];//千万别忘了给k赋初值
int main()
{
	scanf("%d%d", &V, &n);
	for (int i = 1;i <= n;i ++ )
		scanf("%d%d", &v[i], &c[i]);
	for (int i = 1;i <= n;i ++ ){
		for (int j = V;j >= v[i]; j-- )
            f[k][j] = max(f[1 - k][j],f[1 - k][j - v[i]] + c[i]);//j是指所剩的容量
        for (int j = v[i] - 1;j >= 0;j -- )
            f[k][j] = f[1 - k][j];
        k = 1 - k;
	}
	printf("%d",f[1-k][V]);
	return 0;
}
/*其实,明白了一维做法,k也就很好理解了
通过你的一双慧眼,你会发现:原本的二维方法中,在第i个数时,仅仅只用了i-1中得值
然后惊奇的你,明白了:k的作用只是表示了i,1 - k表示了i - 1 
而最后的输出是由于在20行那的k = 1 - k让,存结果的数组成了结果位置的上一个元素*/

然后:

出现了第二题

双塔问题

首先,按照上文所说,分析题目(貌似并没有思路)

再读一遍题,你会看到,两座塔会有那么一丝丝关联(能否使两座塔有同样的高度

这就是解题de关键所在,让我们尝试写出状态式吧

状态定义

首先我们定义一个 f[i][j] ,i 表示第 i 号元素,j 表示两座塔的高度差(也许很奇怪,这里是假设的两座塔) 

接着继续看,很显然,这是一道 01 背包题,必然有放与不放两种状态,接着继续研究放的种情况(既然没有放,肯定没有什么好研究的呀)

   1.万一这块水晶放到较高塔上,就是这样(如图)

则表示高塔的原始高度就是 j - a[i](因为较大塔是加过第 i 号塔的高度的)

则就有这样的式子:dp[i][j] = max(dp[i - 1][j - a[i]] + a[i]);

注意这里有一个易错点 j >= a[i]

   2.假如放在较低塔,且放了之后仍然较低,如图

则有:dp[i][j] = max(dp[i - 1][j + a[i]]);

  3. 假如放在较低塔,变高了,如图

则有:dp[i][j] = max(dp[i - 1][a[i] - j] + j);

a[i] - j 为原本小塔的高度(一个二维数组,可以保存三种值,他的值也可以表示一种值呢)

注意,这里又出现了减法,就一定有 a[i] >= j (千万不能忘)

代码(2)


/*
1.将第i块水晶放到较高的塔上 (j >= a[i])
dp[i][j] = dp[i - 1][j -a[i]] + a[i];
2.将这块水晶放到较低的塔上后仍然较低
dp[i][j] = dp[i - 1][j + a[i]];
3.将这块水晶放到较低的踏上后较高(a[i] >= j)
dp[i][j] = dp[i - 1][a[i] - j] + j;
4.不放
*/
#include<cstdio>
#include<cstring>
#define L 100 + 5
#define M 2000 + 5
#define INT_MIN -2005
int max(int x,int y){
    return x < y ? y : x;
}
int n;
int a[L],dp[L][M],b;
int main()
{
    scanf("%d", & n);
    for (int i = 1;i <= n;i ++ ){
        scanf("%d", & a[i]);
        b += a[i];//b表示两塔之间的最大差距,即一塔为0,全部水晶都放在一座塔上
    }
    memset(dp,-0x3f,sizeof(dp));
    dp[0][0] = 0;
    for (int i = 1;i <= n;i ++ ){
        for (int j = b;j >= 0;j -- ){
            dp[i][j] = max(dp[i - 1][j],dp[i - 1][j + a[i]]);//比较不放第 i 块水晶与将第 i 快水晶放在较低踏上仍然低
            if (j >= a[i])
                dp[i][j] = max(dp[i][j],dp[i - 1][j - a[i]] + a[i]);
            if (j <= a[i])
                dp[i][j] = max(dp[i][j],dp[i - 1][a[i] - j] + j);
        }
    }
    if (dp[n][0] <= 0)//dp[n][0]表示放了 n 块水晶后,两塔相差为0的塔的高度的最大值,因为初始值为极小值,所以dp[n][0]到不了的话,就一定小于0
        printf("Impossible\n");
    else
        printf("%d\n",dp[n][0]);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_43904786/article/details/84787517