洛谷P1049——装箱问题

**从搜索到动态规划(逐步理解)
**原题链接
1.搜索
我们先仔细理解这道题,让剩余空间最小,实际上就是让让放入的体积最大。而对于每一个物体,我们都有放入和不放入两种选择。所以下一层搜索要搜索两个。讲解放入代码中更明白一些。

#include<iostream>
using namespace std;
const int MAX = 20005;
int wei[MAX];//储存物体体积的数组,应该用vol...
int _max = 0;//储存最大可的放入的体积
int v, n;//容积,物体数量
int max(int a, int b) {
    return a > b ? a : b;
}
//这里level是递归层数,同时也可当作对数组下一层搜索的起始下标
//ps:这里的搜索还可以写成返回整型变量的形式
void DFS(int level, int sum) {
   //如果sum<v,就取_max和sum中较大的一个
    if (sum <= v) {
        _max = max(_max, sum);
    }
     //第一个递归窗口,如果sum>v说明已经放不下了,return
    else {
        return;
    }
    //第二个递归窗口,如果递归层数等于n,比较一下,也return回去	
    //实际上,这里不写也可以,因为后面的循环已经限制了递归层数
    if (level == n) {
        _max = max(_max, sum);
        return;
    }
    //最后递归主体,面对每一个wei[i]我们都有放入和丢弃两种选择,所以要递归两次
    for (; level < n; level++) {
        DFS(level + 1, sum + wei[level]);
        DFS(level + 1, sum);
    }
}
int main()
{
    cin >> v >> n;
    for (int i = 0; i < n; i++) {
        cin >> wei[i];
    }
    DFS(0, 0);
    cout << v - _max;
    return 0;
}

毫不意外,超时了…
2.记忆化搜索
记忆化搜索的原理就是利用一个数组储存原来递归的结果。对于一个递归过程来讲,可能会有很多重复递归的过程,我们把这些重复的过程用数组记录处理,这就是记忆化。

#include<iostream>
#include<cstring>
using namespace std;
const int MAX = 20005;
int wei[MAX];//储存物体体积的数组,应该用vol..
int v, n;//容积和物体个数
int memory[31][MAX];//记忆数组
int min(int a, int b) {
    return a < b ? a : b;
}
//这里的level可以可以理解为从前level个物体中选取
//这里的rem可以理解为剩余的体积
//综上理解就是,我们在前level个物体中选择,使得rem最小
//如此,就需要让level从n-1到0递归,rem从v开始递归,
//返回的值是从0到n-1个物体中选择,容积为rem时能够剩余的最小空间
int memorySearch(int level, int rem) {
   //temp是记录用的,既作为返回值返回,也为memory数组更新
    int temp;
   //第一个递归窗口,level为-1时就已经不能再向下运行了,所以把此时的剩余的最小空间返回回去
    if (level == -1)
        return rem;
    //第二个递归窗口,如果memory[level][rem] >= 0,说明之前递归已经有了这个过程直接调用
    if (memory[level][rem] >= 0)
        return memory[level][rem];
    //第一个递归主体,如果第level个元素体积大于剩余的容积,那么就不能选择,向level-1递归
    if (wei[level] > rem)
        return memorySearch(level - 1, rem);
    //否则,我们我们就有了两种选择
    //第一种就是选取第level个物体放入容器
    //第二种就是跳过这个物体
    //我们选取这两种方案中遗留空间较小的哪一种
    else {
        temp = min(memorySearch(level - 1, rem), memorySearch(level - 1, rem - wei[level]));
    }
    //把memory[level][rem]更新,并返回回去
    return memory[level][rem] = temp;
}
int main()
{
    memset(memory, -1, sizeof(memory));
    cin >> v >> n;
    for (int i = 0; i < n; i++) {
        cin >> wei[i];
    }
    cout<<memorySearch(n - 1, v);
    
    return 0;
}

3.记忆化搜索(压缩空间)
仔细考虑一下,实际上记忆数组根本就不需要那么多空间。简单点解释,对于上一个记忆数组,我们是把每一个level中剩余的最小容积都储存了进去,但实际上根本就不用这样做。我们可以这样理解这个memory数组,实际上就是在容积为rem时,能够剩余的最小体积。由此,我们把memory数组改为一位数组。

#include<iostream>
#include<cstring>
using namespace std;
const int MAX = 20005;
int wei[MAX];
int _max;
int v, n;
int memory[MAX];
int min(int a, int b) {
    return a < b ? a : b;
}
//基本同上,不在过多解释了
int memorySearch(int level, int rem) {
    if (level == -1)
        return rem;
//注意这个memory数组和之前含义不太一样
//memory[rem]是指在容积为rem时,能够剩余的最小体积
//如果已经更新过,就返回回去
    if (memory[rem] >= 0)
        return memory[rem];
    int temp;
    if (rem < wei[level])
        temp = memorySearch(level - 1, rem);
    else {
        temp = min(memorySearch(level - 1, rem), memorySearch(level - 1, rem - wei[level]));
    }
    return memory[rem] = temp;
}
int main()
{
    memset(memory, -1, sizeof(memory));
    cin >> v >> n;
    for (int i = 0; i < n; i++) {
        cin >> wei[i];
    }
    cout<<memorySearch(n - 1, v);
    return 0;
}

4.动态规划
讲了这么多,到底是为什么呢?这就牵扯到了搜索和动态规划的关系。动态规划实际上是搜索的迭代版本。为什么这样说呢,我们这样来理解。无论是普通的还是压缩空间的搜索,都是从最高层,逐步的向下递归,之后再把值返还回去。而动态规划就是这个搜索的逆过程,既然要上到下,不妨我们直接从底层开始,逐步向上运算。

#include<iostream>
#include<cstring>
using namespace std;
const int MAX = 20005;
int wei[MAX];
int _max;
int v, n;
//我们沿用之前所用的记忆化数组,实际上应该命名为dp
//对于memory[i][j]这里指的是在1到i个物体中选取,容积为j时所能容纳的最大体积
//ps:之所以从1开始而不像之前从0开始,这个后面会讲到
int memory[31][MAX];
int max(int a, int b) {
	return a > b ? a : b;
}
int main()
{
	memset(memory, 0, sizeof(memory));
	cin >> v >> n;
	for (int i = 1; i <= n; i++)
		cin >> wei[i];
//i从1开始,容器可以选择装填前i个物体
	for (int i = 1; i <= n; i++) {
//j可以从0开始,但是没有必要,因为容积为0的时候放不了物体
		for (int j = 1; j <= v; j++) {
//如果第i个物体的质量大于总容积,那就肯定不能再放入了,所以memory[i][j]能放入的最大体积就和上一层一样了
			if (wei[i] > j)
				memory[i][j] = memory[i - 1][j];
//而如果第i个物体体积小于总容积,那么我们依然有两个选择,放或是不放
//不放的话,自然是和上一层一样
//放的话,自然要加上wei[i],但是此时容积变为了j-wei[i],所以就是memory[i - 1][j - wei[i]] + wei[i]
			else {
				memory[i][j] = max(memory[i - 1][j], memory[i - 1][j - wei[i]] + wei[i]);
			}
		}
	}
	cout << v - memory[n][v];
	return 0;
}

动态规划(压缩空间)

#include<iostream>
#include<cstring>
using namespace std;
const int MAX = 20005;
int wei[MAX];
int _max;
int v, n;
//dp数组表示的是容积为j的时候能够容纳的最大体积
int dp[MAX];
int max(int a, int b) {
	return a > b ? a : b;
}
int main()
{
	memset(dp, 0, sizeof(dp));
	cin >> v >> n;
	for (int i = 1; i <= n; i++)
		cin >> wei[i];
//第一层循环表示的是从第1个物体到第n个物体选取
	for (int i = 1; i <= n; i++) {
//第二层循环表示的是容积为v到wei[i]
//之所以wei[i]到v,因为小于v的时候无法放入东西,没有必要判断
//之所以逆序,我给大家举个栗子,v=5,n=2,wei[]={1,2}
//对于这种情况,正序会导致一个物体重复装填
//这样写可能不太易于理解,大家可以拿上述例子,亲自模拟一下
		for (int j = v; j >= wei[i]; j--) {
			dp[j] = max(dp[j], dp[j - wei[i]] + wei[i]);
		}
	}
	cout << v - dp[v];
	return 0;
}


如果有帮助留个赞欧…

发布了14 篇原创文章 · 获赞 3 · 访问量 153

猜你喜欢

转载自blog.csdn.net/IMDASHUAI/article/details/104561375