装箱问题(dp)

问题描述
  有一个箱子容量为V(正整数,0<=V<=20000),同时有n个物品(0<n<=30),每个物品有一个体积(正整数)。
  要求n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。
输入格式
  第一行为一个整数,表示箱子容量;
  第二行为一个整数,表示有n个物品;
  接下来n行,每行一个整数表示这n个物品的各自体积。
输出格式
  一个整数,表示箱子剩余空间。
  样例输入
  24
  6
  8
  3
  12
  7
  9
  7
样例输出
       0
       关于这一题,有两种解法,一种是利用一维数组dp[]来解决,另一种是利用二维数组dp[][]来解决,我们先来讲第二种,比较好理解
       dp[i][j]表示在前i个物品在容量为j的体积中所存放的最大价值,只不过这里的最大价值等价与最大体积,我们在这里应该知道这应该是一个背包问题,对于一个物品,存在着两种情况,放与不放,如果不放则dp[i][j] == dp[i-1][j],因为你们没有放物体,则在逻辑上等价于前一物体,如果放则dp[i][j] == dp[i-1][j-a[i]]+a[i],在前一状态的基础上,加入物体,剩余空间必然减少,价值增大,但是两者又是等价,则可以如是,这一点很难想,也应该是我们所说的背包问题,具体问题请看代码:

#include<iostream>
using namespace std;
int v, n, a[31], dp[31][10000];
/*
	dp[i][j]表示前i个物品放入容量为j的背包中所得到的最大值
	采用二维数组更好理解一些,但是其空间与时间的开销更大 
*/
int main()
{
	int i, j;
	cin>>v>>n;
	for(i = 1; i <= n; i++)
		cin>>a[i];
	for(i = 1; i <= n; i++)
		dp[i][0] = 0;
	for(j = 1; j <= v; j++)
		dp[0][j] = 0;
	for(i = 1; i <= n; i++)
	{
		for(j = 1; j <= v; j++)
		{
			if(j < a[i])//装不下 
				dp[i][j] = dp[i-1][j];
			else
				dp[i][j] = max(dp[i-1][j], dp[i-1][j-a[i]]+a[i]);
		}	
	}
	printf("%d", v-dp[n][v]);
	return 0;	
} 

但是这会超时,只有80%的正确率。
       采用一种更简单的方式,只需要用一维数组即可,dp[j]表示在容量剩余为j的体积中所能装入的最大价值,同样有放与不放两种状态,不放则不变即dp[j] == dp[j],放则dp[j] = dp[j-a[i]]+a[i],也是属于背包问题,剩余容量减少,但是价值增大,两者也是等价,具体问题请看代码:

#include<iostream>
#include<cstring>
using namespace std;
int v, n, a[31], dp[20005]; 
int main()
{
	int i, j;
	cin>>v>>n;
	memset(dp, 0, sizeof(dp));
	for(i = 0; i < n; i++)
	{
		cin>>a[i];
		/*
			针对一个物品只有两种状态,装与不装,这里的j表示剩余容量
			如果装箱,则背包价值为dp[j-a[i]]+a[i],如果不装箱,则为dp[j]
			所以dp[j] = max(dp[j], dp[j-a[i]]+a[i])(伪背包问题)
			//不太好理解可以采用二维数组解题 
		*/
		for(j = v; j >= a[i]; j--)
		{
			dp[j] = max(dp[j], dp[j-a[i]]+a[i]);
		}
	}
	printf("%d", v-dp[v]); 
	return 0;
}

如果还是不太好理解可以先查询关于背包问题的解决方案。

### C++ 中装箱问题的解决方案 #### 方法一:基于贪心算法的第一适应法 一种常见的解决装箱问题的方法是采用 **First Fit Algorithm**,即第一适应法。这种方法通过依次尝试将当前物品放入第一个能够容纳它的箱子中来实现。如果找不到这样的箱子,则创建一个新的箱子。 以下是具体实现代码: ```cpp #include <iostream> #include <vector> #include <numeric> std::vector<std::vector<int>> firstFit(int items[], int n, int binCapacity) { std::vector<std::vector<int>> bins; for (int i = 0; i < n; i++) { bool placed = false; for (auto &bin : bins) { if (std::accumulate(bin.begin(), bin.end(), 0) + items[i] <= binCapacity) { bin.push_back(items[i]); placed = true; break; } } if (!placed) { bins.push_back({items[i]}); } } return bins; } int main() { int items[] = {4, 8, 1, 4, 2, 1}; int binCapacity = 10; int n = sizeof(items) / sizeof(items[0]); auto bins = firstFit(items, n, binCapacity); std::cout << "Bins:\n"; for (const auto &bin : bins) { std::cout << "[ "; for (int item : bin) { std::cout << item << " "; } std::cout << "]\n"; } return 0; } ``` 此方法的时间复杂度为 \(O(n \times m)\),其中 \(n\) 是物品数量,\(m\) 是箱子的数量[^3]。 --- #### 方法二:动态规划求解最优解 另一种更精确但计算成本较高的方法是利用动态规划(Dynamic Programming)。这种技术可以用来寻找最小化所用箱子数目的全局最优解。 下面是一个简单的例子展示如何使用 DP 来解决问题: ```cpp #include <bits/stdc++.h> using namespace std; int dp[31][20001]; int a[31]; int main(){ int v, n; cin >> v >> n; for(int i=1;i<=n;i++){ cin >> a[i]; } for(int i=1;i<=n;i++){ for(int j=1;j<=v;j++){ if(j<a[i]){ dp[i][j]=dp[i-1][j]; } else{ dp[i][j]=max(dp[i-1][j], dp[i-1][j-a[i]]+a[i]); } } } cout<<v-dp[n][v]<<endl; return 0; } ``` 在此代码片段中,`v` 表示单个箱子的最大容量,而 `n` 则表示待分配到这些箱子中的物品总件数。最终输出的是未能被填满的空间大小[^4]。 --- #### 方法三:简单模拟——逐项放置策略 对于初学者来说,最直观的方式可能是直接模拟整个过程。如下所示的一个版本展示了如何逐一处理每一个项目并决定它应该进入哪个现有的容器或者开启新的容器: ```cpp #include <iostream> using namespace std; struct Item { int weight; int index = 0; } thing[1000]; int main() { int N; int box[1000]; int num = 0; int i, j; cin >> N; for(i=1;i<=N;i++) cin >> thing[i].weight; for(i=1;i<=N;i++) box[i] = 100; for(i=1;i<=N;i++) { int index_box; for(j=1;j<=N;j++) { if(box[j]>=thing[i].weight){ index_box=j; box[j]-=thing[i].weight; break; } } thing[i].index=index_box; } for(i=1;i<=N;i++) { if(box[i]!=100) num++; } cout << num << endl; return 0; } ``` 这段程序首先读取输入数据,接着初始化所有可能使用的盒子为空状态,之后按照顺序检查每一件商品能否放进任何一个已有的盒子里;如果不能则新开设一个盒子[^1]。 --- ### 总结 以上介绍了三种不同的解决 C++ 装箱问题的技术路线图,分别是基于贪心思想的第一适配法则、运用动态规划寻求最佳解答以及较为基础的手动仿真方式。开发者可以根据实际需求选择最适合自己的那一条路径去开发应用软件产品。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值