2023牛客寒假:清楚姐姐学01背包(Hard Version)

清楚姐姐学01背包(Hard Version)

题目描述

本题有对应的Easy version,区别仅在于数据范围,见输入描述,保证easy version的测试用例集是hard version的子集。

清楚姐姐最近学会了01背包,01背包是背包问题中最简单的问题。


01背包的约束条件是给定几种物品,每种物品有且只有一个,并且有权值和体积两个属性。在01背包问题中,因为每种物品只有一个,对于每个物品只需要考虑选与不选两种情况。

如果不选择将其放入背包中,则不需要处理。如果选择将其放入背包中,由于不清楚之前放入的物品占据了多大的空间,需要枚举将这个物品放入背包后可能占据背包空间的所有情况。

现在清楚姐姐有NNN个蝴蝶结,第iii个蝴蝶结的体积为wiw_{i}wi​,好看程度为viv_{i}vi​,她准备了一个容量大小为MMM的包包。她可以从这NNN个蝴蝶结中任选若干个放入背包,但是所选蝴蝶结的体积总和不能大于背包的容量MMM,清楚姐姐想要让所选蝴蝶结的好看程度总和最大化。

她运用自己刚刚学会的01背包知识,快速算出了她能用她的包包装下蝴蝶结好看程度总和的最大值。

现在清楚姐姐有了一个新的问题,我们定义原问题的答案,即所选蝴蝶结好看程度总和的最大值为ValmaxVal_{max}Valmax​。

定义从这NNN个蝴蝶结中去掉第iii个蝴蝶结后,从剩余N−1N-1N−1个蝴蝶结中任选若干个放入背包,所选蝴蝶结好看程度总和的最大值为Vali′Val'_{i}Vali′​。

若Vali′<ValmaxVal'_{i}<Val_{max}Vali′​<Valmax​,则称第iii个蝴蝶结为一个“必选蝴蝶结”。

清楚姐姐现在获得了调整蝴蝶结好看程度的机会,她想要知道,对于第iii个蝴蝶结,在它初始好看程度的基础上,再加上多少,该蝴蝶结就能够成为一个“必选蝴蝶结”。

输入描述:

第一行输入两个正整数N,M(1≤N,M≤5000)N,M(1 \leq N,M \leq 5000)N,M(1≤N,M≤5000)。
接下来NNN行,每行输入两个正整数wi,vi(1≤wi≤M,vi≤109)w_i,v_i(1 \leq w_i \leq M, v_i \leq 10^9)wi​,vi​(1≤wi​≤M,vi​≤109)表示每个蝴蝶结的体积以及好看程度

输出描述:

输出NNN行,每行一个整数,第iii行表示在它初始好看程度的基础上,再加上多少,该蝴蝶结就能够成为一个“必选蝴蝶结”。特别的,如果该蝴蝶结已经是一个“必选蝴蝶结”,则输出000。

示例1

输入

复制4 100 100 100 99 10 1 2 5 5

4 100
100 100
99 10
1 2
5 5

输出

复制0 89 89 94

0
89
89
94

示例2

输入

复制3 1 1 100 1 100 1 100

3 1
1 100
1 100
1 100

输出

复制1 1 1

1
1
1

思路:易知答案为强制不选某一件减去强制选了某一件加一与0取大值

max(0ll,vmx-vmi+1)

考虑从前往后和从后往前分别做dp,好处是把除了某一位以外的dp拆成了两部分

从前后分别取出总空间不大于原来背包总空间的两个值

暴力枚举复杂度为o(m^{2}),显然不行

考虑线性求前后序列前i位各自的最大值

具体代码见下

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5010;

int n,m;
ll w[N],v[N];
ll dpz[N][N];//前i个装满j的空间所能达到的最大值 
ll dpf[N][N];//后i个装满j的空间所能达到的最大值 
ll mxz[N],mxf[N];//对于前某一个i位,mxz[j]表示j容量下能到的最大价值 

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n>>m;
	for(int i=1; i<=n; i++) {
		cin>>w[i]>>v[i];
	}
	for(int i=1; i<=n; i++) {
		for(int j=0; j<=m; j++) {
			dpz[i][j]=dpz[i-1][j];
			if(j-w[i]>=0) dpz[i][j]=max(dpz[i][j],dpz[i-1][j-w[i]]+v[i]);
		}
	}
	reverse(w+1,w+1+n);
	reverse(v+1,v+1+n);
	for(int i=1; i<=n; i++) {
		for(int j=0; j<=m; j++) {
			dpf[i][j]=dpf[i-1][j];
			if(j-w[i]>=0) dpf[i][j]=max(dpf[i][j],dpf[i-1][j-w[i]]+v[i]);
		}
	}
	reverse(w+1,w+1+n);
	reverse(v+1,v+1+n);
	for(int i=1; i<=n; i++) {
		memset(mxz,0,sizeof(mxz));
		memset(mxf,0,sizeof(mxf));
		for(int j=1; j<=m; j++) {
			mxz[j]=max(mxz[j-1],dpz[i-1][j]);//如思路所说的线性处理 
		}
		for(int j=1; j<=m; j++) {
			mxf[j]=max(mxf[j-1],dpf[n-i][j]);
		}
		ll vmx=0,vmi=0;
		for(int j=0; j<=m; j++) {
			vmx=max(vmx,mxz[j]+mxf[m-j]);//求强制不选的最大值 
			if(m-j-w[i]>=0) {
				vmi=max(vmi,mxz[j]+mxf[m-j-w[i]]+v[i]);//求强制选了的最大值 
				//因为强制选了,空间一定要预留,价值一定要加上 
			}
		}
		cout<<max(0ll,vmx-vmi+1)<<"\n";
	}
	return 0;
}

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

心刍

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

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

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

打赏作者

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

抵扣说明:

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

余额充值