2021-07-02 暑训开端——重见dp
Posted KaaaterinaX
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2021-07-02 暑训开端——重见dp相关的知识,希望对你有一定的参考价值。
一、背包问题全集
1、01背包
每个物品只有一个
代码有注释行,放上普通版与滚动数组优化版
普通版:
01背包问题
#define ll long long
using namespace std;
const int maxn=1e3+7;
int v[maxn];
int w[maxn];
int dp[maxn][maxn];//选到第几件物品,当前背包内装的物品体积总和,当前价值为
int main(){
int N,V;
read(N);//快读,代码省略了
read(V);
for(int i=1;i<=N;i++){
read(v[i]);
read(w[i]);
}
dp[0][0]=0;
for(int i=1;i<=N;i++){
for(int j=0;j<=V;j++){
if(j<v[i]){
dp[i][j]=dp[i-1][j];
}
else{
dp[i][j]=max(dp[i-1][j-v[i]]+w[i],dp[i-1][j]);
}
//cout<<dp[i][j]<<' ';
}
//cout<<endl;
}
int ans=0;
for(int i=0;i<=V;i++){
ans=max(ans,dp[N][i]);
}
cout<<ans<<endl;
}
滚动数组版:
#define ll long long
using namespace std;
const int maxn=1e3+7;
int v[maxn];
int w[maxn];
int last[maxn];//当前背包内装的物品总体积为,当前价值为
int now[maxn];
int main(){
int N,V;
read(N);
read(V);
for(int i=1;i<=N;i++){
read(v[i]);
read(w[i]);
}
last[0]=0;
for(int i=1;i<=N;i++){
for(int j=0;j<=V;j++){
if(j+v[i]>V){
now[j]=last[j];
}
else{
now[j]=max(last[j+v[i]]+w[i],last[j]);
}
last[j]=now[j];
//cout<<now[j]<<' ';
}
//cout<<endl;
}
int ans=0;
for(int i=0;i<=V;i++){
ans=max(now[i],ans);
}
cout<<ans<<endl;
}
2、完全背包
每个物品有无限个
一般思路:(在我没学之前想到的,n^3写法)
因为每一种物品都有无限个,那么选到每种物品,可以有多种状态转移的办法。
可以考虑这样的状态转移:
for(int i=1;i<=N;i++){
for(int j=0;j<=V;j++){
//枚举第i个物品选几个
for(int k=0;k*v[i]<=j;k++){
dp[i][j]=max(dp[i-1][j-k*v[i]]+k*w[i],dp[i][j]);
}
}
}
但是由于复杂度过大,会TLE。
于是分析状态转移过程,上段代码的
dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i],dp[i-1][j-2*v[i]+2*v[i] ... );
dp[i][j-v]=max(. dp[i-1][j-v[i],dp[i-1][j-2*v[i]]+w[i] ... );
可知,dp[i][j]=max(dp[i-1][j],dp[i-1][j-v]+w[i])
这样以来,复杂度被压缩到n^2
using namespace std;
const int maxn=1e3+7;
int v[maxn];
int w[maxn];
int dp[maxn][maxn];//选到第几类物品,当前背包内已有的物品总体积,当前价值为
int main(){
int N,V;
read(N);
read(V);
for(int i=1;i<=N;i++){
read(v[i]);
read(w[i]);
}
for(int i=1;i<=N;i++){
//枚举第i个物品选几个
for(int j=0;j<=V;j++){
dp[i][j]=dp[i-1][j];
if(j>=v[i]){
dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]);
}
}
}
int ans=0;
for(int i=0;i<=V;i++){
ans=max(dp[N][i],ans);
}
cout<<ans<<endl;
}
这个也可以优化成滚动数组版。
3、多重背包
每个物品有有给定数目个
这类问题,一般思路可以想出一个n^3的写法,但是这种显然会在数据量稍稍上去一点就TLE。
这个时候可以用到一种特殊的优化方式——二进制优化。
设每种物品有S个。
假如S=100个,考虑将这个物品打包成1,2,4,…,32以及(100-63)个一组,每个包裹最多被选择一次。(2n+1-1 <= S)
运用数学归纳法可以证明存在一种选法,能凑出0~100中任意一个数。
所以,可以考虑将每一种物品打包成1,…,
2n ,
s-((2n+1)-1),转化为01背包问题。
多重背包问题 II
代码实现:
using namespace std;
const int maxn=2e4+5000;
int dp[maxn];
int v[maxn];
int w[maxn];
int s[maxn];
int nv[maxn];
int nw[maxn];
int main(){
int N,V;
cin>>N>>V;
int cnt=0;
for(int i=1;i<=N;i++){
cin>>v[i]>>w[i]>>s[i];
int k=1;
while(k<=s[i]){
cnt++;
nv[cnt]=v[i]*k;
nw[cnt]=w[i]*k;
s[i]-=k;
cout<<k<<endl;
k*=2;
}
if(s[i]>0){
cout<<s[i]<<endl;
cnt++;
nv[cnt]=v[i]*s[i];
nw[cnt]=w[i]*s[i];
}
}
//01背包
for(int i=1;i<=cnt;i++){
for(int j=V;j>=nv[i];j--){
dp[j]=max(dp[j],dp[j-nv[i]]+nw[i]);
}
}
cout<<dp[V]<<endl;
}
其实这个还可以继续优化,下面引入单调队列优化:
(咕咕咕)
4、分组背包
有多组物品,每组只能选x个
以上是关于2021-07-02 暑训开端——重见dp的主要内容,如果未能解决你的问题,请参考以下文章
[D. Johnny and Contribution] 思维 暑训第二天