题目描述
本题有对应的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(),显然不行
考虑线性求前后序列前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;
}