青春没有售价,dp速学一下。
参考文章
在01背包问题中,每个物品只能放一次进背包。
d p [ i ] [ j ] dp[i][j] dp[i][j]:第i个物品,j容量
状态转移公式: f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i − 1 ] [ j − w [ i ] ] + p r i [ i ] ) f[i][j]=max(f[i-1][j],f[i-1][j-w[i]]+pri[i]) f[i][j]=max(f[i−1][j],f[i−1][j−w[i]]+pri[i])
其中 f [ i − 1 ] [ j ] f[i-1][j] f[i−1][j]表示不放该物品, f [ i − 1 ] [ j − w [ i ] ] + p r i [ i ] f[i-1][j-w[i]]+pri[i] f[i−1][j−w[i]]+pri[i]放
遍历每列时为了使用到 i − 1 i-1 i−1行的数据,注意要从后向前遍历。
为什么从后向前遍历更新:
最小的背包容量开始考虑放物品(即正序遍历),那么在更新较大的背包容量 j 时,较小的背包容量 j-v[i] 可能已经考虑过了物品 i。这会导致物品 i 被错误地计算两次,即它在更新 f[j-v[i]] 时被考虑过一次,在更新 f[j] 时又被考虑。
因为逆序是从大到小考虑,所以,并不会发生上述重复考虑的情况( 内层循环中,逆序一开始时,先考虑最大的容量是否能放入,这样处理,就可以使f[j-v[i]] + w[i]只计算一次,不会重复计算 )
原文链接:https://blog.csdn.net/2301_79558858/article/details/137546255
map<string,int>a,b,c;
int dp[N],val[N];//把物品都分到一个个小格子里面
//01背包
void solve(){
int m,n;
cin>>m>>n;
m=21-m;
forr(i,1,n){
int aa,bb,cc;
cin>>aa>>bb>>cc;
string s;cin>>s;
a[s]+=aa;
b[s]=bb;
c[s]=cc;
}
int cnt=1;
for(auto &i:a){
string s=i.first;
while (i.second>=c[s])
{
val[cnt++]=c[s]*b[s];
i.second-=c[s];
}
if(i.second){
val[cnt++]=i.second*b[s];
}
}
forr(i,1,cnt-1){//对每件物品
reforr(j,1,m){//对背包格子容量
dp[j]=max(dp[j],dp[j-1]+val[i]);
}
}
cout<<dp[m]<<endl;
}
每种物品都有无限件可用。
f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i ] [ j − k ∗ w [ i ] ] + k ∗ p r i [ i ] ) , 0 ≤ k ⋅ w [ i ] ≤ j f[i][j]=max(f[i][j],f[i][j-k*w[i]]+k*pri[i]),0\leq k·w[i]\leq j f[i][j]=max(f[i][j],f[i][j−k∗w[i]]+k∗pri[i]),0≤k⋅w[i]≤j
对一些特定数据,减少件数。
01背包从后向前遍历,保证每件物品只选一次
scanf("%d %d",&t,&m);
for(int i=0;i<m;i++){
scanf("%d %d",&w[i],&pri[i]);
//for(int p=t;p>=0;p--){
// if(ti[i]<=p){ p>=ti[i]时才操作
for(int p=w[i];p<=t;p++){//从前往后 跟自己比 能放则放
//一维数组优化
dp[p]=max(dp[p],dp[p-w[i]]+pri[i]);
}
}
每件物品最多有t[i]件可用
f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i ] [ j − k ∗ w [ i ] ] + k ∗ p r i [ i ] ) , 0 ≤ k ⋅ w [ i ] ≤ t [ i ] f[i][j]=max(f[i][j],f[i][j-k*w[i]]+k*pri[i]),0\leq k·w[i]\leq t[i] f[i][j]=max(f[i][j],f[i][j−k∗w[i]]+k∗pri[i]),0≤k⋅w[i]≤t[i]
所以再加一层循环枚举件数
int dp[N];
void solve(){
int n,m;cin>>n>>m;
vector<int>a(n+1);
forr(i,1,n){
cin>>a[i];
}
dp[0]=1;
forr(i,1,n){
reforr(j,0,m){
forr(k,1,min(a[i],j)){//三重循环
dp[j]=(dp[j]+dp[j-k])%MOD;
}
}
}
cout<<dp[m]<<endl;
}
void solve(){
int n,W;
cin>>n>>W;
forr(i,1,n){
cin>>v[i]>>w[i]>>m[i];
}
//二进制拆分
int nn=0;
forr(i,1,n){
for(int j=1;j<=m[i];j<<=1){//把m[i]个物品分成 1 2 4 8 16...
m[i]-=j;
nw[++nn]=j*w[i];
nv[nn]=j*v[i];
}
if(m[i]){//有剩下的
nw[++nn]=m[i]*w[i];
nv[nn]=m[i]*v[i];
}
}
//01背包
vector<int>dp(W+1,0);
forr(i,1,nn){
for(int j=W;j>=nw[i];j--){
dp[j]=max(dp[j],dp[j-nw[i]]+nv[i]);
}
}
cout<<dp[W]<<endl;
}
int w[7]={0,1,2,3,5,10,20};
void solve(){
vector<int>c;
c.push_back(0);
int n=0;
forr(i,1,6){
int a;cin>>a;
forr(j,1,a){
c.push_back(w[i]);//把多重背包变成01背包
n++;
}
}
int sum=0;
for(auto i:c)sum+=i;
vector<int>dp(sum+2,0);//dp[j] 转移到j的方案数
dp[0]=1;
forr(i,1,n){
for(int j=sum;j>=c[i];j--){
dp[j]+=dp[j-c[i]];
}
}
int ans=0;
forr(i,1,sum){
if(dp[i])ans++;
}
cout<<"Total="<<ans<<endl;
}
int w[7]={0,1,2,3,5,10,20};
bitset<1010>b;//第i位是1->i是可以枚举到的数
void solve(){
vector<int>c;
c.push_back(0);
int n=0;
b[0]=1;//初始是一个砝码不用
forr(i,1,6){
int a;cin>>a;
forr(j,1,a){
b|=(b<<w[i]);//上一个状态+w[i]->下一个状态
}
}
int ans=b.count()-1;//-1因为不包括一个砝码也不用的情况
//bitset count()位上为1的个数
cout<<"Total="<<ans<<endl;
}
有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)
物品被划分为若干组,每组中的物品互相冲突,最多选一件。
f [ k ] [ j ] = m a x ( f [ k − 1 ] [ j ] , f [ k − 1 ] [ j − w [ i ] ] + p [ i ] , 物品 i ∈ 组 k f[k][j]=max(f[k-1][j],f[k-1][j-w[i]]+p[i],物品i \in组k f[k][j]=max(f[k−1][j],f[k−1][j−w[i]]+p[i],物品i∈组k
forr(i,1,n){//组数
cin>>m;
forr(j,1,m)cin>>w[j]>>p[j];//组中的物品
//下与01背包类似
reforr(k,0,V){//容量
forr(j,1,m){
if(k>=w[j])dp[k]=max(dp[k],dp[k-w[j]]+p[j]);
}
}
}