题目描述:
题目链接:
https://www.acwing.com/problem/content/12/
思路: 这个题有一定的难度,思想很妙,第一次做的话不是很容易想到怎么处理。
比较直观的思路是用dp,f[i][j] 表示使用1~i的物品,且体积小于等于j 的最大价值, g[i][j] 表示到达f[i][j]这个状态的最后一步最优决策。 这样整个dp数组更新完之后,就可以从n往前推,得到整个最优决策序列,但是这种做法没法保证得到的解是“字典序最小的”。
yxc老师说,一般让我们求解“字典序最小的方案”,常用的做法是“贪心”。 如何贪心? 我们先从1号物品考虑,关于1号物品的选取会有3种情况:
情况1: 1号物品一定要选(dp转移式中更优的决策), 那么此时一定要选1号物品
情况2: 1号物品一定不能选(dp转移式中更不优的决策), 那么此时一定不选1号物品
情况3: 1号物品可选可不选(dp转移式中两种决策价值相同), 那么此时一定要选1号物品,因为1号物品是字典序最小的,如果不选,则不可能最优。
在保证1号物品处的决策是最优的前提下(贪心的前提),继续考虑2号物品是否选取,从前往后不断考虑。
这个过程启发我们,dp数组可以从后往前更新。这样在最后就是从1往后推,得到整个最优决策序列,并且能够保证字典序是最小的。
具体来说,f[i][j] 的定义: i~n 这些物品中,体积小于等于j的最大价值
g[i][j] 表示到达f[i][j]这个状态的最后一步最优决策.
f[i][j] =max(f[i+1][j] , f[i+1][j-v[i]]+w[i]); 若两者相等,则优先选取后者(因为他的字典序更小)
最后从1开始往后推,就可以得到最终答案了。 需要注意一些边界情况。
AC代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1010;
const int inf=-0x3f3f3f3f;
int f[N][N],g[N][N];
int n,m;
int v[N],w[N];
int main(void){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d%d",&v[i],&w[i]);
}
for(int i=0;i<=m;i++) g[n+1][i]=n+1;
for(int i=n;i>=1;i--){
for(int j=0;j<=m;j++){
f[i][j]=f[i+1][j];
g[i][j]=g[i+1][j];
if(j>=v[i]&&f[i][j]<=f[i+1][j-v[i]]+w[i]){
g[i][j]=i;
f[i][j]=f[i+1][j-v[i]]+w[i];
}
}
}
int cur=m;
int now_ind=1;
while(cur>0 && now_ind<=n){
int x=g[now_ind][cur];
if(x>n) break;
printf("%d ",x);
cur-=v[x];
now_ind=x+1;
}
return 0;
}