一个小老鼠有 m m m 次偷东西机会,每次背包大小为 s z i sz_i szi ,有 n n n 件物品,大小 a i a_i ai ,价值 b i b_i bi ,每次只能顺序拿去(或不拿),求最大的价值和。
保证 s z i > s z i − 1 sz_i>sz_{i-1} szi>szi−1
1 ≤ n , a i , s z i ≤ 200 1\leq n,a_i,sz_i \leq 200 1≤n,ai,szi≤200
1 ≤ m , b i ≤ 1 0 5 1\leq m,b_i \leq 10^5 1≤m,bi≤105
一道明显的DP题,如果只看一次机会就是裸的01背包。
考虑枚举机会转移的时机,将容量,当前位置写进转移式子,
这是一个裸暴力(悲)。
转移的复杂度大致可以估算为 O ( N 3 ∗ m ) O(N^3*m) O(N3∗m)。
时间明显超标。
我们转念一想,容量对于最大值的计算实际上帮助不大,
由于数据在 200 200 200 ,
我们可以提前打出某容量在一段区间里的最大价值。
容易想到 f l , r , s z = m a x ( f l , r − 1 , s z − a r + b r , f l , r − 1 , s z ) f_{l,r,sz}=max(f_{l,r-1,sz-a_r}+b_r,f_{l,r-1,sz}) fl,r,sz=max(fl,r−1,sz−ar+br,fl,r−1,sz) 。
容量的计算就可以舍去了。
我们重新定义DP式子,将机会和当前位置写入转移式。
容易得到
d p x , i = m a x ( d p x − 1 , y + f y + 1 , i , s z i ) ( 0 ≤ y ≤ i ) dp_{x,i}=max(dp_{x-1,y}+f_{y+1,i,sz_i})(0\leq y\le i) dpx,i=max(dpx−1,y+fy+1,i,szi)(0≤y≤i)
复杂度为 O ( N 2 ∗ m ) O(N^2 *m) O(N2∗m)
我们发现 s z sz sz的值是递增的,单独考虑,一个大的背包造成的贡献一定优于或等于一个小的背包。
所以我们只需要选取最后 n n n 组背包求解即可。
复杂度优化为 O ( N 3 ) O(N^3) O(N3)
#include
using namespace std;
const int N=205;
int dp[N][N];
int f[N][N][N];
int a[N],b[N];
int sz[N];
int n,m,tot,ans;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
scanf("%d%d",&a[i],&b[i]);
for(int i=1;i<=m;i++)
{
int a;
scanf("%d",&a);
if(i+n>=m)
sz[++tot]=a; //递增的特殊性
}
for(int l=1;l<=n;l++)
for(int r=l;r<=n;r++)
{
for(int siz=a[r];siz<=200;siz++) //预处理容量
f[l][r][siz]=max(f[l][r-1][siz-a[r]]+b[r],f[l][r-1][siz]);
for(int k=0;k<=200;k++)
f[l][r][k]=max(f[l][r][k],f[l][r-1][k]);
}
for(int i=1;i<=tot;i++)
for(int j=1;j<=n;j++)
for(int y=0;y<=j;y++)
{
dp[i][j]=max(dp[i-1][y]+f[y+1][j][sz[i]],dp[i][j]);
ans=max(dp[i][j],ans);
}
printf("%d",ans);
}
注意看题目给出的特殊的数据。