超大完全背包

本文介绍了一种解决大容量完全背包问题的有效算法。通过将原问题分解并利用多层DP数组进行状态转移,解决了传统方法中背包容量过大导致的空间限制问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

普通的完全背包我们都会,但是要是背包的容量炒鸡大,dp数组开不下怎么办呢?下面敬上超大完全背包模板一份。

大闹上兰帝国

时间限制: C/C++ 20000ms; Java 40000ms 内存限制: 65535KB
通过次数: 9 总提交次数: 20
问题描述

       由于zbt日常搞事情,早已弄得上兰帝国天翻地覆。于是很快被SHlong列入了上兰帝国重点教育对象,zbt作为一个不受驯服的人当然要奋起反抗了,于是他开始四处破坏,SHlong由于能力有限不能降服zbt,于是只能去ACM实验室请来了声望极高的Wukan方丈,Wukan方丈对zbt说:“听说你十分擅长传说中的背包问题,那么我就出一个完全背包,你若是答得出来就让你随便搞事情。你要是答不出来,那我就要把你赶出上兰帝国了”。zbt当然随口答应了,于是Wukan方丈就开始出题了。

       有一个容量为S的背包,和N种物品,每种物品都有一个体积和价值,每种物品可以无限使用。求容量不超过S的情况下价值的能放入背包的最大价值。

       zbt看了下题嘲讽道:“这不是一道大水题吗”,话音未落,他突然发现貌似存在一些小(很大的)问题。于是他偷偷的使用传音之术来请你帮助他来解决这个问题。


输入描述

输入包含多组数据

每组数据第一行有两个正整数N,S分别代表物品的种类和背包的容量。

后面N行每行两个数v,w代表每种物品的体积和价值。

1<=N,v<=1000;

0<=S,w<=1000000000;


输出描述

对于每组数据分别输出一行,每行只有一个正整数,代表可以放入的最大的价值。


样例输入
3 100
14 5
13 2
5 1
样例输出
35
来源
第三届山西省大学生程序设计大赛

#include <iostream>
#include <cstdio>
#include <cstring>
#define me(x,y) memset(x,y,sizeof(x))
#define sd(x) scanf("%d",&x)
#define ss(x) scanf("%s",x)
#define sf(x) scanf("%f",&x)
#define slf(x) scanf("%lf",&x)
#define slld(x) scanf("%lld",&x)
#define pd(x) printf("%d\n",x)
#define plld(x) printf("%lld\n",x)
#define ps(x) printf("%s\n",x)
#define max(x,y) (x>=y?x:y)
#define min(x,y) (x<y?x:y)
#define sum(x,y) (x+y)
#define INF 0x3f3f3f3f

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn=1005;

ll n,c;
ll dp[maxn*5];
ll dp2[55][maxn*5];
ll w[maxn],v[maxn];
ll low[50];

void init() {
    me(dp,0);
    me(dp2,0);
    me(low,0);
}

int main() {
    while(scanf("%lld%lld",&n,&c)!=EOF) {
        ll maxv=0;
        for(int i=1;i<=n;i++) {
            slld(v[i]);
            slld(w[i]);
            maxv=max(maxv,v[i]);
        }
        init();
        ll s=c;
        ll cnt=0;
        while(s>0){
            low[++cnt]=s;
            s=((s-maxv)>>1);
        }
        for(ll i=1;i<=n;i++)
            for(ll j=v[i];j<=maxv*4;j++)
                dp[j]=max(dp[j],dp[j-v[i]]+w[i]);

        for(ll i=cnt;i>=1;i--){
            for(ll j=low[i];j<=low[i]+maxv*2;j++){
                if(j<=4*maxv) dp2[i][j-low[i]]=dp[j];
                else{
                    for(ll k=(j-maxv)/2;k<=j/2;k++)            //下一层
                        dp2[i][j-low[i]]=max(dp2[i][j-low[i]],dp2[i+1][k-low[i+1]]+dp2[i+1][j-k-low[i+1]]);
                }
            }
        }
        plld(dp2[1][0]);
    }
	return 0;
}

### 01背包问题中的逆向遍历 在解决01背包问题时,为了防止状态转移过程中重复计算同一个物品多次放入的情况,通常采用 **逆向遍历** 的方式来更新动态规划数组 `f`。具体而言,在外层循环枚举每一个物品的同时,内层循环会从大到小(即从总容量 `j=m` 开始逐步减少至当前物品体积 `v`),从而确保每件物品只被考虑一次。 以下是基于上述逻辑的具体实现: ```cpp #include <iostream> using namespace std; const int MAX_M = 1000; // 背包最大容量 int f[MAX_M + 1]; // 动态规划数组 void zeroOneKnapsack(int n, int m) { for (int i = 1; i <= n; ++i) { // 遍历每个物品 int v, w; cin >> v >> w; // 输入第i个物品的价值w和重量v for (int j = m; j >= v; --j) { // 倒序遍历背包剩余空间 f[j] = max(f[j], f[j - v] + w); // 更新最优解 } } } int main() { int n, m; cin >> n >> m; // 输入物品数量n和背包容量m memset(f, 0, sizeof(f)); // 初始化dp数组为0 zeroOneKnapsack(n, m); cout << f[m] << endl; // 输出最终的最大价值 return 0; } ``` #### 关键点解析 1. **为什么需要倒序遍历?** 如果按照正序遍历,则当更新某个位置的状态时,可能会用到之前已经更新过的更小的位置状态,这相当于允许同一件物品被多次选取,违背了01背包问题的要求——每种物品最多选一次[^1]。 2. **时间复杂度分析:** 外层循环执行次数为 `O(n)`,而内层循环每次迭代的时间复杂度取决于背包容量大小 `O(m)`。因此整体算法的时间复杂度为 \( O(n \times m) \)[^2]。 3. **空间优化:** 上述代码通过一维数组实现了二维DP表的空间压缩版本。原版的二维形式可能定义为 `dp[i][j]` 表示前 `i` 个物品装入容量为 `j` 的背包所能获得的最大价值。但在实际操作中可以通过调整遍历方向仅保留最后一行的结果即可完成相同功能[^3]。 --- ### §相关问题§ 1. 如何修改以上程序使其支持多维度约束条件下的01背包问题? 2. 完全背包与多重背包的区别是什么?如何利用类似的思路设计它们各自的解决方案? 3. 是否存在其他方法代替传统的动态规划解决此类问题?如果有,请举例说明。 4. 当面对超大规模的数据集时,应采取哪些策略进一步提升效率或者降低内存消耗? 5. 对于某些特殊场景下(如负权值情况),现有模型是否仍然适用?如果不适用该如何改进?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水能zai舟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值